diff --git a/Scripts/Modeling/Manage/Rename.py b/Scripts/Modeling/Manage/Rename.py new file mode 100644 index 0000000..5b46228 --- /dev/null +++ b/Scripts/Modeling/Manage/Rename.py @@ -0,0 +1,419 @@ + +''' +from importlib import reload +import Rename +reload(Rename) +Rename.UI() +''' +from Modeling.Manage import Rename +# this text can be entered from the script editor and can be made into a button + +import maya.cmds as cmds # type: ignore + +def UI(): + + global SelectName + global RenameText + + global StartValue + global PaddingValue + global NumberCheck + + global RemoveFirst + global RemoveEnd + + global PrefixText + global SuffixText + + global SearchText + global ReplaceText + global SRCheck + + #UI Width + sizeX = 240 + version = "v1.0" + if cmds.window("RenameWin", exists=True): + cmds.deleteUI("RenameWin", window=True) + + #Creating UI + igEzRenamWin = cmds.window("RenameWin", title="Rename Tool "+version, width=sizeX+6, height=385, mnb = True, mxb = False, sizeable = False) + + #Creating interface elements + mainLayout = cmds.columnLayout("mainColumnLayout", width = sizeX, adjustableColumn=False, co = ["both",2]) + + #Select All Button + cmds.separator(h=5, style = "none", parent = mainLayout) + cmds.button(label = "Select All", w=sizeX, h=25, c=SelectAll, ann = "Select ALL objects in scene") + cmds.separator(h=5, style = "none", parent = mainLayout) + + #Select by Name + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 25), (2, 60)], cs = [(5,5), (5,5)]) + cmds.button(label = "Select by Name", w=sizeX/3, h=25, c=SelectName, align = "Center", ann="Select objects by name") + SelectName = cmds.textField(w = sizeX*0.646, ann="Select by Name \n Use * after and/or before the text to select by prefix/suffix \n Example: *_grp") + cmds.separator(w = sizeX, h=15, style = "in", parent = mainLayout) + + #Rename and Number + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)]) + #Rename Field + cmds.text(label=" Rename:", font = "boldLabelFont", w = sizeX/4, align="left", ann="Write the name you want to rename the objects selected") + RenameText = cmds.textField(w = sizeX*0.75, ann="Write the name you want to rename the objects selected") + + #Start Field + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)]) + cmds.text(label=" Start:", font = "boldLabelFont", w = sizeX/4, align="left") + StartValue = cmds.textField(w = sizeX/4, tx="1", ann="Write the Start value for the sequence") + #Padding Field + cmds.text(label=" Padding:", font = "boldLabelFont", w = sizeX/4, align="left") + PaddingValue = cmds.textField(w = sizeX/4, tx="2", ann="Write the Padding value for the numbers") + #Number Letters + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)]) + cmds.text(label="", font = "boldLabelFont", w = sizeX/4-2, align="left") + NumberCheck = cmds.radioButtonGrp(labelArray2=[ 'Numbers', 'Letters'], numberOfRadioButtons=2, w=sizeX, h=20, sl=1, cw = ([1,120])) + #ButtonRename and Number + cmds.separator(h=5, style = "none", parent = mainLayout) + cmds.button(label = "Rename and Number", w=sizeX, h=25, c=RenameNumber, align = "Center", parent = mainLayout) + cmds.separator(w = sizeX, h=15, style = "in", parent = mainLayout) + + #RemoveCharacter + #Remove First/Last + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 25), (2, 60)], cs = [(5,5)]) + cmds.text(label=" Remove:", font = "boldLabelFont", w = sizeX/3-12, align="left") + cmds.button(label = "First Char->", w=sizeX/3, h=25, c=lambda *args: Rename.Remove(True), align = "Center") + cmds.button(label = "<-Last Char", w=sizeX/3, h=25, c=lambda *args: Rename.Remove(False), align = "Center") + cmds.separator(h=5, style = "none", parent = mainLayout) + + #Remove pasted__ + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 25), (2, 60)], cs = [(90,90)]) + cmds.text(label=" ", font = "boldLabelFont", w = sizeX/3-12, align="left") + cmds.button(label = "pasted__", w=sizeX/3, h=25, c=RemovePasted, align = "Center") + + #Remove UI + cmds.separator(h=5, style = "none", parent = mainLayout) + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 25), (2, 60)], cs = [(8.5,8.5)]) + RemoveFirst = cmds.textField(w = sizeX/5, tx="0", ann="Write the amount of characters you want to delete on text beginning") + cmds.button(label = "-", w=25, h=25, c=lambda *args: Rename.RemoveChar('begin'), align = "Center", ann="Delete on text beginning") + cmds.button(label = "Remove", w=sizeX/4, h=25, c=lambda *args: Rename.RemoveChar('all'), align = "Center", ann="Delete on text beginning and ending") + cmds.button(label = "-", w=25, h=25, c=lambda *args: Rename.RemoveChar('end'), align = "Center", ann="Delete on text ending") + RemoveEnd = cmds.textField(w = sizeX/5, tx="3", ann="Write the amount of characters you want to delete on text ending") + cmds.separator(w = sizeX, h=15, style = "in", parent = mainLayout) + + #Suffix + #Control Suffix + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)], cs = [(5,5)]) + cmds.text(label=" Prefix:", font = "boldLabelFont", w = sizeX/4-10, align="left", ann="Write the prefix") + PrefixText = cmds.textField(w = sizeX/2.5+33, tx="prefix_", ann="Write the prefix") + cmds.button(label = "Add", w=sizeX/4-10, h=25, c=lambda *args: Rename.PrefixSuffix(False), align = "Center") + cmds.separator(h=5, style = "none", parent = mainLayout) + #Group Suffix + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)], cs = [(5,5)]) + cmds.text(label=" Suffix:", font = "boldLabelFont", w = sizeX/4-10, align="left", ann="Write the suffix") + SuffixText = cmds.textField(w = sizeX/2.5+33, tx="_suffix", ann="Write the suffix") + cmds.button(label = "Add", w=sizeX/4-10, h=25, c=lambda *args: Rename.PrefixSuffix(True), align = "Center") + cmds.separator(w = sizeX, h=15, style = "in", parent = mainLayout) + + #Prefix + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)], cs = [(5,5)]) + cmds.button(label = "_Grp", w=sizeX/5-4, h=25, c=lambda *args: Rename.Suffix('_Grp'), align = "Center", ann = "Add Grp suffix") + cmds.button(label = "_Geo", w=sizeX/5-4, h=25, c=lambda *args: Rename.Suffix('_Geo'), align = "Center", ann = "Add Geo suffix") + cmds.button(label = "_Ctrl", w=sizeX/5-4, h=25, c=lambda *args: Rename.Suffix('_Ctrl'), align = "Center", ann = "Add Ctrl suffix") + cmds.button(label = "_Jnt", w=sizeX/5-4, h=25, c=lambda *args: Rename.Suffix('_Jnt'), align = "Center", ann = "Add Jnt suffix") + cmds.button(label = "_Drv", w=sizeX/5-4, h=25, c=lambda *args: Rename.Suffix('_Drv'), align = "Center", ann = "Add Drv suffix") + cmds.separator(w = sizeX, h=15, style = "in", parent = mainLayout) + + #Search and Replace + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)], cs = [(5,5)]) + cmds.text(label=" Search:", font = "boldLabelFont", w = sizeX/4-10, align="left", ann="Write the text to search") + SearchText = cmds.textField(w = sizeX/2+100, ann="Write the text to search") + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)], cs = [(5,5)]) + cmds.text(label=" Replace:", font = "boldLabelFont", w = sizeX/4-10, align="left", ann="Write the text to replace") + ReplaceText = cmds.textField(w = sizeX/2+100, ann="Write the text to replace") + cmds.rowColumnLayout( numberOfRows=1, w=sizeX, parent=mainLayout, rowHeight=[(1, 20), (2, 60)], cs = [(5,5)]) + SRCheck = cmds.radioButtonGrp(labelArray3=[ 'Selected', 'Hierarchy', 'All'], numberOfRadioButtons=3, w=sizeX, h=20, sl=1, cw = ([1,95],[2,95],[3,95])) + cmds.button(label = "Apply", w=sizeX, h=25, c=SearchReplace, align = "Center", parent = mainLayout) + cmds.separator(h=5, style = "none", parent = mainLayout) + + #Show UI: + cmds.showWindow(igEzRenamWin) + +def SelectAll(*args): + cmds.select(ado=True, hi = True) + selection = cmds.ls(selection=True, sn=True) + selectionAdd = [] + + #Old select all code version + """for objs in selection: + children = cmds.listRelatives(objs, c=True, f =True) + print "Children01:", children + shapes = cmds.listRelatives(objs, s=True, f = True) + print "Shapes:", shapes + + if not children == None: + if not shapes == None: + for s in shapes: + children.remove(s) + + for chi in children: + + children2 = cmds.listRelatives(chi, c=True, f = True) + print "CHildren02:", children2 + + if not children2 == None: + for chi2 in children2: + children.append(chi2) + + selectionAdd.append(chi) + + + + for objs in selectionAdd: + cmds.select(objs, add=True)""" + +def SelectName(*args): + cmds.select(cl=True) + name = cmds.textField(SelectName, text = 1, q=True) + try: + selection = cmds.ls(name, l = True) + except: + cmds.warning("Object Don't Exist") + + for objs in selection: + cmds.select(objs, add=True) + +def RenameNumber(*args): + + number = cmds.textField(StartValue, text = 1, q=True) + number = int(number) + + padding = cmds.textField(PaddingValue, text = 1, q=True) + padding = int(padding) + + NumberLetters = cmds.radioButtonGrp(NumberCheck, q=True, select=True) + + newName = cmds.textField(RenameText, text = 1, q=True) + + selection = cmds.ls(selection=True, sn=True) + + lettersList = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] + + #Number suffix + y = 0 + + for obj in selection: + try: + #Teste if has duplicate mesh with the same name on the scene and return the name without parents + trueName = testDuplicateName(obj) + #Save the original name + oldName = trueName + + #If true use numbers, else use letters + if NumberLetters == 1: + zeros = "" + for x in range(int(padding)): + if len(str(number)) <= x: + zeros = zeros+"0" + + #Create the newName and rename + name = newName+"_"+"{:0>"+str(padding)+"}" + newNameList = name.format(number) + cmds.rename(obj, name.format(number)) + + else: + newNameList = newName+"_"+lettersList[y] + cmds.rename(obj, newName+"_"+lettersList[y]) + if y < len(lettersList)-1: + y+=1 + else: + y=0 + + #For to rename all the oldNames on list to newNames + for x in range(len(selection)): + newParentName = selection[x].replace(oldName, newNameList) + selection[x] = newParentName + except: + pass + + number = int(number+1) + +def RemoveChar(Type): + + first = cmds.textField(RemoveFirst, text = 1, q=True) + end = cmds.textField(RemoveEnd, text = 1, q=True) + + selection = cmds.ls(selection = True, sn=True) + + for objs in selection: + #Teste if has duplicate mesh with the same name on the scene + trueName = testDuplicateName(objs) + + #Save the original name + oldName = trueName + + if Type == "all": + name = trueName[:-int(end)] + name = name[int(first):] + + if Type == "begin": + name = trueName[int(first):] + + if Type == "end": + name = trueName[:-int(end)] + + try: + cmds.rename(objs, str(name)) + except: + pass + + #For to rename all the oldNames on list to newNames + for x in range(len(selection)): + newParentName = selection[x].replace(oldName, name) + selection[x] = newParentName + + +def Remove(Type): + + selection = cmds.ls(selection = True, sn = True) + + for objs in selection: + #Teste if has duplicate mesh with the same name on the scene + trueName = testDuplicateName(objs) + + #Save the original name + oldName = trueName + + if Type: + name = trueName[1:] + else: + name = trueName[:-1] + + try: + cmds.rename(objs, name) + except: + pass + + #For to rename all the oldNames on list to newNames + for x in range(len(selection)): + newParentName = selection[x].replace(oldName, name) + selection[x] = newParentName + + + +def RemovePasted(*args): + + selection = cmds.ls("pasted__*", sn = True) + + for objs in selection: + #Teste if has duplicate mesh with the same name on the scene + trueName = testDuplicateName(objs) + + name = trueName[8:] + try: + cmds.rename(objs, name) + except: + cmds.warning("Don't Exist pasted Objects") + +def PrefixSuffix(Suffix): + prefix = cmds.textField(PrefixText, text = 1, q=True) + suffix = cmds.textField(SuffixText, text = 1, q=True) + + selection = cmds.ls(selection = True, sn = True) + + for objs in selection: + + #Teste if has duplicate mesh with the same name on the scene + trueName = testDuplicateName(objs) + #Save the original name + oldName = trueName + + if Suffix: + name = str(trueName)+suffix + else: + name = prefix+str(trueName) + + try: + cmds.rename(objs, name) + except: + pass + + #For to rename all the oldNames on list to newNames + for x in range(len(selection)): + newParentName = selection[x].replace(oldName, name) + selection[x] = newParentName + + +def Suffix(Text): + + selection = cmds.ls(selection = True, sn = True) + + for objs in selection: + #Test if has duplicate mesh with the same name on the scene + trueName = testDuplicateName(objs) + + #Save the original name + oldName = trueName + + newName = trueName+Text + try: + cmds.rename(objs, newName) + except: + pass + + #For to rename all the oldNames on list to newNames + for x in range(len(selection)): + newParentName = selection[x].replace(oldName, newName) + selection[x] = newParentName + +def SearchReplace(*args): + + search = cmds.textField(SearchText, text = 1, q=True) + replace = cmds.textField(ReplaceText, text = 1, q=True) + + SRMethod = cmds.radioButtonGrp(SRCheck, q=True, select=True) + + #Selected search and Replace method + if SRMethod == 1: + selection = cmds.ls(selection = True, sn = True) + + #Hierarchy search and Replace method + if SRMethod == 2: + cmds.select(hi = True) + selection = cmds.ls(selection = True, sn = False) + + #All search and Replace method + if SRMethod == 3: + #Select All DagObjects in scene + selection = [] + cmds.select(ado = True, hi = True) + selection = cmds.ls(selection = True, sn=False) + + #for to rename the objects + for obj in selection: + #Teste if has duplicate mesh with the same name on the scene and return the name without parents + trueName = testDuplicateName(obj) + #Save the original name + oldName = trueName + #Search and replace to create the new name + newName = trueName.replace(search, replace) + + #Rename the object + try: + cmds.rename(obj, newName) + except: + pass + + #For to rename all the oldNames on list to newNames + for x in range(len(selection)): + newParentName = selection[x].replace(oldName, newName) + selection[x] = newParentName + + print("Slecao: ", selection) + + + +def testDuplicateName(Obj): + + try: + trueName = Obj.split("|") + return trueName[len(trueName)-1] + except: + return Obj + diff --git a/Scripts/Modeling/Select/EdgeLoopSmartSelect.jpg b/Scripts/Modeling/Select/EdgeLoopSmartSelect.jpg new file mode 100644 index 0000000..96c66ab Binary files /dev/null and b/Scripts/Modeling/Select/EdgeLoopSmartSelect.jpg differ diff --git a/Scripts/Modeling/Select/EdgeLoopSmartSelect.py b/Scripts/Modeling/Select/EdgeLoopSmartSelect.py new file mode 100644 index 0000000..16a2233 --- /dev/null +++ b/Scripts/Modeling/Select/EdgeLoopSmartSelect.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import maya.cmds as cmds +import maya.OpenMaya as om +import maya.OpenMayaUI as omui +from maya.OpenMaya import MGlobal +import math +import re +import maya.mel as mel + +def run(): + global selInitialGeo + global storeAllEdges + selInitialGeo = [] + storeAllEdges = [] + testAnySel = cmds.ls(sl=1) + if testAnySel: + checkCurrentSelEdge = cmds.filterExpand(sm=32) + checkCurrentSelPoly = cmds.filterExpand(sm=12) + checkCurrentSelOther =cmds.filterExpand(sm=(31,34,35) ) + if checkCurrentSelEdge: + checkEdgeLoopGrp = getEdgeRingGroup(checkCurrentSelEdge) + if len(checkEdgeLoopGrp) == 1: + listAAA = '' + try: + listAAA = vtxLoopOrder(checkCurrentSelEdge) + except: + pass + if listAAA: + storeAllEdges = checkCurrentSelEdge + checkCurrentSelPoly = cmds.ls(hl=1) + selInitialGeo.append(checkCurrentSelPoly[0]) + if not cmds.attributeQuery('start', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long', ln='start') + if not cmds.attributeQuery('end', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='end') + if not cmds.attributeQuery('nearest', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='nearest') + if not cmds.attributeQuery('closeTo', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='closeTo') + if not cmds.attributeQuery('firstRun', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='firstRun') + cmds.setAttr((selInitialGeo[0]+'.firstRun'),0) + selShortestLoop() + else: + cmds.select(checkCurrentSelEdge) + else: + cmds.select(checkCurrentSelEdge) + else: + if checkCurrentSelOther: + checkCurrentSelPoly = cmds.ls(hl=1) + selInitialGeo.append(checkCurrentSelPoly[0]) + CMD = 'doMenuComponentSelectionExt("' + str(checkCurrentSelPoly[0])+ '", "edge", 0);' + mel.eval(CMD) + cmds.select(cl=1) + cmds.polyCrease((checkCurrentSelPoly[0]+'.e[*]'), value=0) + if not cmds.attributeQuery('start', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long', ln='start') + if not cmds.attributeQuery('end', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='end') + if not cmds.attributeQuery('nearest', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='nearest') + if not cmds.attributeQuery('closeTo', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='closeTo') + if not cmds.attributeQuery('firstRun', node = selInitialGeo[0], ex=True ): + cmds.addAttr(selInitialGeo[0], at = 'long',ln='firstRun') + cmds.setAttr((selInitialGeo[0]+'.firstRun'),0) + cmds.scriptJob ( runOnce=True, event = ["SelectionChanged", selShortestLoop]) + else: + print('no selection') + +def selShortestLoop(): + global ctx + global storeAllEdges + global selInitialGeo + global storeCameraPosition + global initialList + initialList = [] + storeAllEdges = cmds.ls(sl=1,fl=1) + listVtx = vtxLoopOrder(storeAllEdges) + initialList = listVtx + startID = listVtx[0].split('[')[-1].split(']')[0] + endID = listVtx[-1].split('[')[-1].split(']')[0] + cmds.setAttr((selInitialGeo[0]+'.start'),int(startID)) + cmds.setAttr((selInitialGeo[0]+'.end'),int(endID)) + view = omui.M3dView.active3dView() + cam = om.MDagPath() + view.getCamera(cam) + camPath = cam.fullPathName() + cameraTrans = cmds.listRelatives(camPath,type='transform',p=True) + storeCameraPosition = cmds.xform(cameraTrans,q=1,ws=1,rp=1) + if cmds.objExists('preSelDisp')==0: + cmds.createNode("creaseSet") + cmds.rename("preSelDisp") + cmds.sets((selInitialGeo[0]+".e[*]"),remove="preSelDisp") + cmds.sets(storeAllEdges,forceElement="preSelDisp") + cmds.setAttr("preSelDisp.creaseLevel", 1) + cmds.polyOptions(dce=1) + ctx = 'Click2dTo3dCtx' + if cmds.draggerContext(ctx, exists=True): + cmds.deleteUI(ctx) + cmds.draggerContext(ctx, ppc= liveShortPause ,rc = liveShortPause, dragCommand = liveShortMove, fnz = liveShortStop ,name=ctx, cursor='crossHair',undoMode='step') + cmds.setToolTo(ctx) + + + +def liveShortMove(): + global selInitialGeo + global ctx + global screenX,screenY + global storeCameraPosition + global storeAllEdges + modifiers = cmds.getModifiers() + if selInitialGeo: + vpX, vpY, _ = cmds.draggerContext(ctx, query=True, dragPoint=True) + pos = om.MPoint() + dir = om.MVector() + hitpoint = om.MFloatPoint() + omui.M3dView().active3dView().viewToWorld(int(vpX), int(vpY), pos, dir) + pos2 = om.MFloatPoint(pos.x, pos.y, pos.z) + checkHit = 0 + finalMesh = [] + finalX = 0 + finalY = 0 + finalZ = 0 + shortDistance = 10000000000 + distanceBetween = 1000000000 + hitFacePtr = om.MScriptUtil().asIntPtr() + hitFace = [] + for mesh in selInitialGeo: + selectionList = om.MSelectionList() + selectionList.add(mesh) + dagPath = om.MDagPath() + selectionList.getDagPath(0, dagPath) + fnMesh = om.MFnMesh(dagPath) + intersection = fnMesh.closestIntersection( + om.MFloatPoint(pos2), + om.MFloatVector(dir), + None, + None, + False, + om.MSpace.kWorld, + 99999, + False, + None, + hitpoint, + None, + hitFacePtr, + None, + None, + None) + if intersection: + x = hitpoint.x + y = hitpoint.y + z = hitpoint.z + finalX = x + finalY = y + finalZ = z + hitFace = om.MScriptUtil(hitFacePtr).asInt() + hitFaceName = (selInitialGeo[0] + '.f[' + str(hitFace) +']') + cpX,cpY,cpZ = getPolyFaceCenter(hitFaceName) + shortDistanceCheck = 10000 + checkCVDistance = 10000 + cvList = (cmds.polyInfo(hitFaceName , fv=True )[0]).split(':')[-1].split(' ') + cvListX = [x for x in cvList if x.strip()] + + for v in cvListX: + checkNumber = ''.join([n for n in v.split('|')[-1] if n.isdigit()]) + if len(checkNumber) > 0: + cvPoint = (selInitialGeo[0] + '.vtx[' + str(checkNumber) +']') + cvPosition = cmds.pointPosition(cvPoint) + checkCVDistance = math.sqrt( ((float(cvPosition[0]) - finalX)**2) + ((float(cvPosition[1]) - finalY)**2) + ((float(cvPosition[2]) - finalZ)**2)) + if checkCVDistance < shortDistanceCheck: + shortDistanceCheck = checkCVDistance + mostCloseCVPoint = cvPoint + mostCloseCVPointPos = cvPosition + + newID = mostCloseCVPoint.split('[')[-1].split(']')[0] + cmds.setAttr((selInitialGeo[0]+'.nearest'),int(newID)) + checkRun = cmds.getAttr(selInitialGeo[0]+'.firstRun') + + if checkRun == 1: + checkCloseTo = cmds.getAttr(selInitialGeo[0]+'.nearest') + checkStart = cmds.getAttr(selInitialGeo[0]+'.start') + checkEnd = cmds.getAttr(selInitialGeo[0]+'.end') + + startPosition = cmds.pointPosition( (selInitialGeo[0] + '.vtx[' + str(checkStart) +']')) + endPosition = cmds.pointPosition( (selInitialGeo[0] + '.vtx[' + str(checkEnd) +']')) + nearPosition = cmds.pointPosition( (selInitialGeo[0] + '.vtx[' + str(checkCloseTo) +']')) + + distA = math.sqrt( ((float(nearPosition[0]) - startPosition[0])**2) + ((float(nearPosition[1]) - startPosition[1])**2) + ((float(nearPosition[2]) - startPosition[2])**2)) + distB = math.sqrt( ((float(nearPosition[0]) - endPosition[0])**2) + ((float(nearPosition[1]) - endPosition[1])**2) + ((float(nearPosition[2]) - endPosition[2])**2)) + if distA > distB: + cmds.setAttr((selInitialGeo[0]+'.end'),checkEnd) + cmds.setAttr((selInitialGeo[0]+'.start'),checkStart) + else: + cmds.setAttr((selInitialGeo[0]+'.end'),checkStart) + cmds.setAttr((selInitialGeo[0]+'.start'),checkEnd) + checkStart = cmds.getAttr(selInitialGeo[0]+'.start') + checkEnd = cmds.getAttr(selInitialGeo[0]+'.end') + checkNearest = cmds.getAttr(selInitialGeo[0]+'.nearest') + #get shortest + cmds.select(cl=1) + cmds.sets((selInitialGeo[0]+".e[*]"),remove="preSelDisp") + sortList = [] + if modifiers == 4: # "press Ctrl" + PA = (selInitialGeo[0] + '.vtx[' + str(checkEnd) +']') + PB = (selInitialGeo[0] + '.vtx[' + str(checkNearest) +']') + UVA = cmds.polyListComponentConversion(PA,fv =1 ,tuv=1) + UVB = cmds.polyListComponentConversion(PB,fv =1 ,tuv=1) + startUVID = UVA[0].split('[')[-1].split(']')[0] + endUVID = UVB[0].split('[')[-1].split(']')[0] + #cmds.polySelect(selInitialGeo[0] , shortestEdgePathUV=(int(startUVID), int(endUVID))) + listShort = cmds.polySelect(selInitialGeo[0] ,q=1, shortestEdgePathUV=(int(startUVID), int(endUVID))) + if listShort: + for l in listShort: + sortList.append(selInitialGeo[0]+'.e['+ str(l) +']' ) + else: + #cmds.polySelect(selInitialGeo[0] , shortestEdgePath=(int(checkEnd), int(checkNearest))) + #maya run faster without select + listShort = cmds.polySelect(selInitialGeo[0] ,q=1, shortestEdgePath=(int(checkEnd), int(checkNearest))) + if listShort: + for l in listShort: + sortList.append(selInitialGeo[0]+'.e['+ str(l) +']' ) + getC = storeAllEdges + sortList + cmds.select(getC) + cmds.sets(getC,forceElement="preSelDisp") + if modifiers == 1: # "press Shelf" + liveList = cmds.sets("preSelDisp",q=1) + liveList = cmds.ls(liveList,fl=1) + listVtx = vtxLoopOrder(liveList) + checkEndID = listVtx[-1].split('[')[-1].split(']')[0] + extEdgeA = [] + extEdgeB = [] + if int(checkEndID) == checkNearest: + extEdgeA = cmds.polyListComponentConversion(listVtx[-1],fv =1 ,te=1) + extEdgeB = cmds.polyListComponentConversion(listVtx[-2],fv =1 ,te=1) + else: + extEdgeA = cmds.polyListComponentConversion(listVtx[0],fv =1 ,te=1) + extEdgeB = cmds.polyListComponentConversion(listVtx[1],fv =1 ,te=1) + extEdgeA = cmds.ls(extEdgeA,fl=1) + extEdgeB = cmds.ls(extEdgeB,fl=1) + extEdge = list(set(extEdgeA) - (set(extEdgeA)-set(extEdgeB))) + checkExtLoop = cmds.polySelectSp(extEdge,q=1, loop=1) + checkExtLoop = cmds.ls(checkExtLoop,fl=1) + otherList = list(set(checkExtLoop) - set(liveList)) + checkEdgeLoopGrp = getEdgeRingGroup(otherList) + if checkEdgeLoopGrp: + if len(checkEdgeLoopGrp) > 0: + if len(checkEdgeLoopGrp) == 1: + cmds.sets(otherList,forceElement="preSelDisp") + elif len(checkEdgeLoopGrp)> 1: + for c in checkEdgeLoopGrp: + cvList = cmds.polyListComponentConversion (c,fe=1,tv=1) + cvList = cmds.ls(cvList,fl=1) + if (selInitialGeo[0]+'.vtx['+ str(checkNearest) +']' ) in cvList: + cmds.sets(c,forceElement="preSelDisp") + cmds.select("preSelDisp", add=1) + cmds.refresh(cv=True,f=True) + + +def liveShortPause(): + global storeAllEdges + global selInitialGeo + global initialList + checkRun = cmds.getAttr(selInitialGeo[0]+'.firstRun') + if checkRun == 0: + cmds.setAttr((selInitialGeo[0]+'.firstRun'),1) + else: + cmds.setAttr((selInitialGeo[0]+'.firstRun'),2) + getEdgeList = cmds.ls(sl=1,fl=1) + storeAllEdges = getEdgeList + listVtx = vtxLoopOrder(getEdgeList) + if len(listVtx)>0: + listHead = listVtx[0] + listEnd = listVtx[-1] + if listHead in initialList: + cmds.setAttr((selInitialGeo[0]+'.start'),int(listVtx[0].split('[')[-1].split(']')[0])) + cmds.setAttr((selInitialGeo[0]+'.end'),int(listVtx[-1].split('[')[-1].split(']')[0])) + + else: + cmds.setAttr((selInitialGeo[0]+'.start'),int(listVtx[-1].split('[')[-1].split(']')[0])) + cmds.setAttr((selInitialGeo[0]+'.end'),int(listVtx[0].split('[')[-1].split(']')[0])) + else: + liveShortStop() + + +def liveShortStop(): + cmds.setToolTo("moveSuperContext") + cmds.polyOptions(dce=0) + if cmds.objExists('preSelDisp'): + cmds.setAttr("preSelDisp.creaseLevel", 0) + cmds.delete('preSelDisp') + + +def getPolyFaceCenter(faceName): + meshFaceName = faceName.split('.')[0] + findVtx = cmds.polyInfo(faceName, fv=1) + getNumber = [] + checkNumber = ((findVtx[0].split(':')[1]).split('\n')[0]).split(' ') + for c in checkNumber: + findNumber = ''.join([n for n in c.split('|')[-1] if n.isdigit()]) + if findNumber: + getNumber.append(findNumber) + centerX = 0 + centerY = 0 + centerZ = 0 + for g in getNumber: + x,y,z = cmds.pointPosition((meshFaceName + '.vtx['+g + ']'),w=1) + centerX = centerX + x + centerY = centerY + y + centerZ = centerZ + z + + centerX = centerX/len(getNumber) + centerY = centerY/len(getNumber) + centerZ = centerZ/len(getNumber) + return centerX,centerY,centerZ + +def vtxLoopOrder(edgelist): + if edgelist: + selEdges = edgelist + shapeNode = cmds.listRelatives(selEdges[0], fullPath=True , parent=True ) + transformNode = cmds.listRelatives(shapeNode[0], fullPath=True , parent=True ) + edgeNumberList = [] + for a in selEdges: + checkNumber = ((a.split('.')[1]).split('\n')[0]).split(' ') + for c in checkNumber: + findNumber = ''.join([n for n in c.split('|')[-1] if n.isdigit()]) + if findNumber: + edgeNumberList.append(findNumber) + getNumber = [] + for s in selEdges: + evlist = cmds.polyInfo(s,ev=True) + checkNumber = ((evlist[0].split(':')[1]).split('\n')[0]).split(' ') + for c in checkNumber: + findNumber = ''.join([n for n in c.split('|')[-1] if n.isdigit()]) + if findNumber: + getNumber.append(findNumber) + dup = set([x for x in getNumber if getNumber.count(x) > 1]) + getHeadTail = list(set(getNumber) - dup) + vftOrder = [] + finalList = [] + if len(getHeadTail)>0: + vftOrder.append(getHeadTail[0]) + count = 0 + while len(dup)> 0 and count < 100: + checkVtx = transformNode[0]+'.vtx['+ vftOrder[-1] + ']' + velist = cmds.polyInfo(checkVtx,ve=True) + getNumber = [] + checkNumber = ((velist[0].split(':')[1]).split('\n')[0]).split(' ') + for c in checkNumber: + findNumber = ''.join([n for n in c.split('|')[-1] if n.isdigit()]) + if findNumber: + getNumber.append(findNumber) + findNextEdge = [] + for g in getNumber: + if g in edgeNumberList: + findNextEdge = g + edgeNumberList.remove(findNextEdge) + checkVtx = transformNode[0]+'.e['+ findNextEdge + ']' + findVtx = cmds.polyInfo(checkVtx,ev=True) + getNumber = [] + checkNumber = ((findVtx[0].split(':')[1]).split('\n')[0]).split(' ') + for c in checkNumber: + findNumber = ''.join([n for n in c.split('|')[-1] if n.isdigit()]) + if findNumber: + getNumber.append(findNumber) + gotNextVtx = [] + for g in getNumber: + if g in dup: + gotNextVtx = g + if len(gotNextVtx)> 0: + dup.remove(gotNextVtx) + vftOrder.append(gotNextVtx) + count += 1 + if len(getHeadTail)>1: + vftOrder.append(getHeadTail[1]) + for v in vftOrder: + finalList.append(transformNode[0]+'.vtx['+ v + ']' ) + return finalList + +def getEdgeRingGroup(selEdges): + if selEdges: + trans = selEdges[0].split(".")[0] + e2vInfos = cmds.polyInfo(selEdges, ev=True) + e2vDict = {} + fEdges = [] + for info in e2vInfos: + evList = [ int(i) for i in re.findall('\\d+', info) ] + e2vDict.update(dict([(evList[0], evList[1:])])) + while True: + try: + startEdge, startVtxs = e2vDict.popitem() + except: + break + edgesGrp = [startEdge] + num = 0 + for vtx in startVtxs: + curVtx = vtx + while True: + + nextEdges = [] + for k in e2vDict: + if curVtx in e2vDict[k]: + nextEdges.append(k) + if nextEdges: + if len(nextEdges) == 1: + if num == 0: + edgesGrp.append(nextEdges[0]) + else: + edgesGrp.insert(0, nextEdges[0]) + nextVtxs = e2vDict[nextEdges[0]] + curVtx = [ vtx for vtx in nextVtxs if vtx != curVtx ][0] + e2vDict.pop(nextEdges[0]) + else: + break + else: + break + num += 1 + fEdges.append(edgesGrp) + retEdges =[] + for f in fEdges: + collectList=[] + for x in f: + getCom= (trans +".e["+ str(x) +"]") + collectList.append(getCom) + retEdges.append(collectList) + return retEdges + +run() \ No newline at end of file diff --git a/Scripts/Modeling/Select/IntervalSelectEdge.py b/Scripts/Modeling/Select/IntervalSelectEdge.py new file mode 100644 index 0000000..ae1ddf3 --- /dev/null +++ b/Scripts/Modeling/Select/IntervalSelectEdge.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import maya.cmds as cmds +import maya.mel as mel + +def select_edges(): + num_edges_selected = cmds.filterExpand(expand=True, selectionMask=32) + size_components = len(num_edges_selected) if num_edges_selected else 0 + + if size_components == 0: + cmds.error("Select at least one edge before running the script.") + elif size_components == 1: + mel.eval('polySelectEdgesEveryN "edgeRing" 2') + print("1 cycle mode selected.") + elif size_components == 2: + mel.eval('polySelectEdgesEveryN "edgeRing" 4') + print("2 cycle modes selected.") + elif size_components == 3: + mel.eval('polySelectEdgesEveryN "edgeRing" 6') + print("3 cycle modes selected.") + elif size_components == 4: + mel.eval('polySelectEdgesEveryN "edgeRing" 8') + print("4 cycle modes selected.") + elif size_components == 5: + mel.eval('polySelectEdgesEveryN "edgeRing" 10') + print("5 cycle modes selected.") + elif size_components == 6: + mel.eval('polySelectEdgesEveryN "edgeRing" 12') + print("6 cycle modes selected.") + elif size_components == 7: + mel.eval('polySelectEdgesEveryN "edgeRing" 14') + print("7 cycle modes selected.") + elif size_components == 8: + mel.eval('polySelectEdgesEveryN "edgeRing" 16') + print("8 cycle modes selected.") + elif size_components > 8: + cmds.error("Please select 8 or fewer edges.") + +def create_select_edges_window(): + window_name = "IntervalSelectEdges" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + window = cmds.window(window_name, title="Interval Select Edges", toolbox=True, + widthHeight=(300, 150), + sizeable=True, + backgroundColor=(0.25, 0.25, 0.25)) + + main_layout = cmds.columnLayout(adjustableColumn=True) + cmds.text(label="Select edge(s) and click Execute", height=50, width=250, parent=main_layout) + cmds.button(label="Select", command=lambda x: select_edges(), width=250, height=30, backgroundColor=(0.53, 0.81, 0.98), parent=main_layout) + + cmds.window(window, edit=True, widthHeight=(300, 150)) # Set the window size again after creating contents + cmds.showWindow(window) + +# Add a new function as the entry point for the module +def show(): + create_select_edges_window() + +# If the script is run directly (not imported as a module), show the window +if __name__ == "__main__": + show() \ No newline at end of file diff --git a/Scripts/Modeling/Select/SamePositionSelector.py b/Scripts/Modeling/Select/SamePositionSelector.py new file mode 100644 index 0000000..3d04f0e --- /dev/null +++ b/Scripts/Modeling/Select/SamePositionSelector.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import maya.cmds as cmds +import maya.mel as mel + +def save_setting(): + threshold = cmds.floatSliderGrp(SamePositionSelector_UI_Threshold_FS, q=True, v=True) + check_bb = cmds.checkBox(SamePositionSelector_UI_CheckBB_CB, q=True, v=True) + unselect = cmds.checkBox(SamePositionSelector_UI_UnselectBB_CB, q=True, v=True) + cmds.optionVar(fv=("AriSamePositionS_Threshold", threshold)) + cmds.optionVar(iv=("AriSamePositionS_CheckBB", check_bb)) + cmds.optionVar(iv=("AriSamePositionS_Unselect", unselect)) + +def vector_much(val_a, val_b, gosa): + return (val_a[0] <= val_b[0] + gosa and val_a[0] >= val_b[0] - gosa and + val_a[1] <= val_b[1] + gosa and val_a[1] >= val_b[1] - gosa and + val_a[2] <= val_b[2] + gosa and val_a[2] >= val_b[2] - gosa) + +def check_bounding_box(object_list_name): + gosa = cmds.floatSliderGrp(SamePositionSelector_UI_Threshold_FS, q=True, v=True) + object_list_bb_min = [cmds.getAttr(obj + ".boundingBoxMin")[0] for obj in object_list_name] + object_list_bb_max = [cmds.getAttr(obj + ".boundingBoxMax")[0] for obj in object_list_name] + + same_obj_list = [] + for i in range(len(object_list_name)): + bb_min_a, bb_max_a = object_list_bb_min[i], object_list_bb_max[i] + local_counter = 0 + for j in range(i + 1, len(object_list_name)): + if object_list_name[j] in same_obj_list: + continue + bb_min_b, bb_max_b = object_list_bb_min[j], object_list_bb_max[j] + if vector_much(bb_min_a, bb_min_b, gosa) and vector_much(bb_max_a, bb_max_b, gosa): + if local_counter == 0: + same_obj_list.append(object_list_name[i]) + same_obj_list.append(object_list_name[j]) + local_counter += 1 + if local_counter != 0: + same_obj_list.append(";") + return same_obj_list + +def select_list(obj_list): + unselect_true = cmds.checkBox(SamePositionSelector_UI_UnselectBB_CB, q=True, v=True) + first_true = unselect_true + select_list = [] + for obj in obj_list: + if obj != ";": + if first_true: + first_true = False + continue + select_list.append(obj) + else: + if unselect_true: + first_true = True + cmds.select(select_list) + +def same_position_selector_select(*args): + save_setting() + bb_true = cmds.checkBox(SamePositionSelector_UI_CheckBB_CB, q=True, v=True) + gosa = cmds.floatSliderGrp(SamePositionSelector_UI_Threshold_FS, q=True, v=True) + object_list_name = cmds.ls(sl=True, tr=True) + object_list_pos = [cmds.xform(obj, q=True, ws=True, piv=True)[:3] for obj in object_list_name] + + same_obj_list = [] + for i in range(len(object_list_name)): + pos_a = object_list_pos[i] + local_counter = 0 + for j in range(i + 1, len(object_list_name)): + if object_list_name[j] in same_obj_list: + continue + pos_b = object_list_pos[j] + if vector_much(pos_a, pos_b, gosa): + if local_counter == 0: + same_obj_list.append(object_list_name[i]) + same_obj_list.append(object_list_name[j]) + local_counter += 1 + if local_counter != 0: + same_obj_list.append(";") + + if bb_true: + same_bounding_box_list = [] + pair_list = [] + for obj in same_obj_list: + if obj == ";": + same_bounding_box_list.extend(check_bounding_box(pair_list)) + pair_list = [] + else: + pair_list.append(obj) + same_obj_list = same_bounding_box_list + + select_list(same_obj_list) + +def show(): + global SamePositionSelector_UI_Threshold_FS + global SamePositionSelector_UI_CheckBB_CB + global SamePositionSelector_UI_UnselectBB_CB + + threshold = 0.01 + check_bb = True + unselect = True + + if cmds.optionVar(exists="AriSamePositionS_Threshold"): + threshold = cmds.optionVar(q="AriSamePositionS_Threshold") + if cmds.optionVar(exists="AriSamePositionS_CheckBB"): + check_bb = cmds.optionVar(q="AriSamePositionS_CheckBB") + if cmds.optionVar(exists="AriSamePositionS_Unselect"): + unselect = cmds.optionVar(q="AriSamePositionS_Unselect") + + window_name = "SamePositionSelector" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name) + + window = cmds.window(window_name, title="Same Position Selector", tlb=True, + widthHeight=(300, 150), backgroundColor=(0.25, 0.25, 0.25), + sizeable=True) + + label_threshold = "Threshold" + label_check_bb = "Check boundingbox" + label_unselect = "Unselect first object" + + main_layout = cmds.columnLayout(adjustableColumn=True) + + cmds.rowLayout(numberOfColumns=2, columnWidth2=(80, 220), adjustableColumn=2, parent=main_layout) + cmds.text(label=label_threshold) + SamePositionSelector_UI_Threshold_FS = cmds.floatSliderGrp(field=True, minValue=0.0, maxValue=0.1, fieldMinValue=0.0, fieldMaxValue=1000, value=threshold, precision=6) + cmds.setParent('..') + + cmds.columnLayout(adjustableColumn=True, parent=main_layout) + SamePositionSelector_UI_CheckBB_CB = cmds.checkBox(label=label_check_bb, value=check_bb) + SamePositionSelector_UI_UnselectBB_CB = cmds.checkBox(label=label_unselect, value=unselect) + cmds.setParent('..') + + cmds.separator(height=10, style='in', parent=main_layout) + + cmds.button(label="Select", command=same_position_selector_select, backgroundColor=(0.53, 0.81, 0.98), height=30, parent=main_layout) + + cmds.window(window, edit=True, widthHeight=(300, 150)) + cmds.showWindow(window) + +if __name__ == "__main__": + show() \ No newline at end of file diff --git a/Scripts/Modeling/UV/RizomBridge/RizomUVBridge.py b/Scripts/Modeling/UV/RizomBridge/RizomUVBridge.py new file mode 100644 index 0000000..0651faa --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/RizomUVBridge.py @@ -0,0 +1,869 @@ +import os, platform, subprocess, sys + +if sys.version_info.minor < 11: + # Maya 2024 and earlier + from PySide2 import QtGui + from PySide2 import QtWidgets, QtCore + from shiboken2 import wrapInstance +else: + # Maya 2025 + from PySide6 import QtGui + from PySide6 import QtWidgets, QtCore + from shiboken6 import wrapInstance + +if sys.version_info.major == 3: + # Python 3 + from . import scriptlist +elif sys.version_info.major == 2: + # Python 2 + import scriptlist + +from maya.app.general.mayaMixin import MayaQWidgetDockableMixin + +from maya import OpenMayaUI as omui +import maya.OpenMaya as om + +import maya.cmds as cmds +import maya.mel as mel +import xml.dom.minidom as xml # For XML settings + +DEBUG = False +PRNT_STRING = "" + +# om.MGlobal.displayError('ERROR!') + +class Settings(): + def __init__(self): + self.config_path = os.path.join('%s/RizomBridge' % os.getenv('APPDATA')) + config_file = 'uisettings.xml' + + # TODO: Create a unique id for the lua script tied to the Maya instance. + # rizom_script_path = tempfile.gettempdir() + os.sep + "MayaRizomBridgeScript.lua" + rizom_script_path = os.path.join(self.config_path, "MayaRizomBridgeScript.lua") + self.rizom_script_path = rizom_script_path.replace('\\', '/') + + self.apppath = None + self.config_file_path = os.path.join(self.config_path, config_file) + self.check_config_exists(self.config_path) + + try: + self.read_settings() + except: + print(" Failed to read settings, creating new xml file.") + self.set_defaults() + self.save_xml() + self.read_settings() + + self.reset_export_path() + + def reset_export_path(self): + self.exportFile = os.path.join(self.config_path, self.objname) + self.exportFile = self.exportFile.replace('\\', '/') + + def read_settings(self): + doc = xml.parse(self.config_file_path) + + ui_app = doc.getElementsByTagName("Application")[0] + self.apppath = ui_app.getAttribute("apppath") + self.objname = ui_app.getAttribute("objname") + self.upaxis = ui_app.getAttribute("upaxis") + self.loaduvs = self.str_to_bool(ui_app.getAttribute("loaduvs")) + self.useuvlink = self.str_to_bool(ui_app.getAttribute("useuvlink")) + self.fixuvnames = self.str_to_bool(ui_app.getAttribute("fixuvnames")) + self.usecustompath = self.str_to_bool(ui_app.getAttribute("usecustompath")) + self.custompath = ui_app.getAttribute("custompath") + + ui_packer = doc.getElementsByTagName("Packer")[0] + self.quality = int(ui_packer.getAttribute("quality")) + self.mutations = int(ui_packer.getAttribute("mutations")) + self.margin = int(ui_packer.getAttribute("margin")) + self.spacing = int(ui_packer.getAttribute("spacing")) + self.resolution = int(ui_packer.getAttribute("resolution")) + self.initscaleavg = self.str_to_bool(ui_packer.getAttribute("initscaleavg")) + self.autofit = self.str_to_bool(ui_packer.getAttribute("autofit")) + + def set_defaults(self): + self.objname = "MayaRizomExport.fbx" + self.upaxis = "Y" + self.loaduvs = True + self.useuvlink = True + self.fixuvnames = False + self.usecustompath = False + self.custompath = "" + + self.quality = 2 + self.mutations = 256 + self.margin = 2 + self.spacing = 2 + self.resolution = 1024 + self.initscaleavg = True + self.autofit = True + self.apppath = self.findall_rizom_installs()[-1] + pass + + def save_xml(self): + print(PRNT_STRING, "Saving settings to disk: {}".format(self.config_file_path)) + doc = xml.Document() + root_element = doc.createElement("RizomBridge") + + element_application = doc.createElement("Application") + element_application.setAttribute("apppath", str(self.apppath)) + element_application.setAttribute("objname", str(self.objname)) + element_application.setAttribute("upaxis", str(self.upaxis)) + element_application.setAttribute("loaduvs", str(self.loaduvs)) + element_application.setAttribute("useuvlink", str(self.useuvlink)) + element_application.setAttribute("fixuvnames", str(self.fixuvnames)) + element_application.setAttribute("usecustompath", str(self.usecustompath)) + element_application.setAttribute("custompath", str(self.custompath)) + + element_packer = doc.createElement("Packer") + element_packer.setAttribute("quality", str(self.quality)) + element_packer.setAttribute("mutations", str(self.mutations)) + element_packer.setAttribute("margin", str(self.margin)) + element_packer.setAttribute("spacing", str(self.spacing)) + element_packer.setAttribute("resolution", str(self.resolution)) + element_packer.setAttribute("initscaleavg", str(self.initscaleavg)) + element_packer.setAttribute("autofit", str(self.autofit)) + + doc.appendChild(root_element) + root_element.appendChild(element_application) + root_element.appendChild(element_packer) + doc.writexml(open(self.config_file_path, 'w'), indent=" ", addindent=" ", newl='\n') + + def check_config_exists(self, config_path): + print(PRNT_STRING, "Checking for config file:{}".format(self.config_file_path)) + + if not os.path.exists(config_path): + os.makedirs(config_path) + if not os.path.exists(self.config_file_path): + print(PRNT_STRING, "Config not found.") + self.set_defaults() + self.save_xml() + else: + print(PRNT_STRING, "Config found.") + + def findall_rizom_installs(self): + """ Returns list of all rizom.exe in registry that exist on disk """ + try: + import winreg + except: + self.manual_locate_rizom() + return [self.apppath] + + key_path = "SOFTWARE\\Rizom Lab\\" + parent_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) + installs = [] + i = 0 + while i < 1000: + try: + key = winreg.EnumKey(parent_key, i) + try: + exe_path = winreg.QueryValue(winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path + key), + "rizomuv.exe") + ".exe" + except FileNotFoundError: + exe_path = "" + if os.path.exists(exe_path): + installs.append(exe_path.replace("\\", "/")) + + except WindowsError: + break + i += 1 + print (PRNT_STRING, "Found RizomUV installs in Windows Registry:") + for inst in sorted(installs): + print(" ..", inst) + return sorted(installs) + + def manual_locate_rizom(self): + om.MGlobal.displayWarning("Could not locate rizomuv.exe: %s" % self.apppath) + fname = QtWidgets.QFileDialog.getOpenFileName(None, 'Locate rizomuv.exe', 'C:/Program Files/Rizom Lab/', "Executable (*.exe)") + + self.apppath = fname[0] + self.appver = self.get_version() + self.save_xml() + + def get_version(self): + """ Return version number as list as read from the folder name, eg. [2022, 2] """ + return os.path.dirname(self.apppath).split()[-1].split('.') + + def check_lua_path(self): + if not os.path.exists(self.rizom_script_path): + with open(self.rizom_script_path, 'w') as f: + if DEBUG: print(PRNT_STRING, "Creating blank lua file at", self.rizom_script_path) + f.write('') + + def str_to_bool(self, s): + if s == 'True': + return True + else: + # Return False for everything else, otherwise it might return None which will cause a crash. + return False + + +class RizomUVBridgeWindow(MayaQWidgetDockableMixin, QtWidgets.QDialog): + def __init__(self, rootWidget=None, *args, **kwargs): + super(RizomUVBridgeWindow, self).__init__() + + """ + if not os.path.exists(config_path): + reset_config() + if DEBUG: print("", config_path) + Config.read(config_path) + """ + self.conf = Settings() + self.conf.check_lua_path() + + # self.rizomPath = self.conf.apppath + print("Rizom Path:", self.conf.apppath) + self.setWindowTitle("Bridge") + + self.sj_num_selchange = cmds.scriptJob(parent=self.objectName(), event=['SelectionChanged', self.ui_update_uvchannels]) + + # self.setMinimumSize(200, 300) + self.resize(250, 300) + + self.create_widgets() + self.create_layouts() + self.create_connections() + self.validate_export_path() + self.exported_objects = [] + self.ui_update_uvchannels() + self.cleanse_namespaces() + + if not os.path.exists(self.conf.apppath): + self.conf.manual_locate_rizom() + v = self.conf.get_version() + self.btn_run.setText('Run RizomUV {}.{}'.format(v[0], v[1])) + + rizom_link_path = os.path.dirname(self.conf.apppath) + '/RizomUVLink' + self.port = None + if rizom_link_path not in sys.path: + sys.path.append(os.path.dirname(self.conf.apppath) + '/RizomUVLink') + try: + from RizomUVLink import CRizomUVLink + self.link = CRizomUVLink() + except: + self.link = None + + + # noinspection PyAttributeOutsideInit + def create_widgets(self): + v = ".".join(self.conf.get_version()) + self.btn_run = QtWidgets.QPushButton('Run RizomUV {}'.format(v), self) + self.btn_export = QtWidgets.QPushButton('Export', self) + self.btn_import = QtWidgets.QPushButton('Import', self) + self.cbx_use_link = QtWidgets.QCheckBox('Attempt to use CRizomUVLink', self) + self.cbx_use_link.setChecked(self.conf.useuvlink) + self.cbx_fix_set_names = QtWidgets.QCheckBox('Fix UV set names on Import', self) + self.cbx_fix_set_names.setChecked(self.conf.fixuvnames) + self.cbx_custom_path = QtWidgets.QCheckBox('Use custom path for fbx', self) + self.cbx_custom_path.setChecked(self.conf.usecustompath) + self.field_custom_path = QtWidgets.QLineEdit("C:/") + self.field_custom_path.setText(self.conf.custompath) + + # Export settings widgets + self.radioAxisY = QtWidgets.QRadioButton('Y') + self.radioAxisZ = QtWidgets.QRadioButton('Z') + if self.conf.upaxis == 'Y': + self.radioAxisY.setChecked(True) + else: + self.radioAxisZ.setChecked(True) + + self.cbx_keepuv = QtWidgets.QCheckBox('Load Existing UVs', self) + self.cbx_keepuv.setChecked(self.conf.loaduvs) + + self.combo_scripts = QtWidgets.QComboBox(self) + self.combo_scripts.addItem("No Script") + for s in scriptlist.scripts: + self.combo_scripts.addItem(s[1]) + + # Utilities widgets + self.btn_fix_shell_normals = QtWidgets.QPushButton('Set UV border edges to Hard') + self.btn_edit_settings = QtWidgets.QPushButton('Open settings folder') + + self.combo_pack_uvset = QtWidgets.QComboBox(self) + # + self.btn_pack = QtWidgets.QPushButton('Export and Pack UVs', self) + self.combo_pack_quality = QtWidgets.QComboBox(self) + self.combo_pack_quality.addItems(['Low', 'Normal', 'High', 'Higher', 'Ultra']) + self.combo_pack_quality.setCurrentIndex(self.conf.quality) + # + self.slider_pack_uvmap = QtWidgets.QSlider(QtCore.Qt.Orientation(1)) + self.slider_pack_uvmap.setMinimum(0) + self.slider_pack_uvmap.setMaximum(5) + self.slider_pack_uvmap.setValue(1) + + self.label_uvmap = QtWidgets.QLabel("1") + + self.dspin_pack_mutations = QtWidgets.QSpinBox() + self.dspin_pack_mutations.setSingleStep(1) + self.dspin_pack_mutations.setRange(1, 1000) + self.dspin_pack_mutations.setWrapping(False) + self.dspin_pack_mutations.setValue(self.conf.mutations) + # + self.dspin_pack_resolution = QtWidgets.QSpinBox() + self.dspin_pack_resolution.setSingleStep(8) + self.dspin_pack_resolution.setRange(8, 8192) + self.dspin_pack_resolution.setWrapping(False) + self.dspin_pack_resolution.setValue(self.conf.resolution) + # + self.dspin_pack_margin = QtWidgets.QSpinBox() + self.dspin_pack_margin.setSingleStep(1) + self.dspin_pack_margin.setWrapping(False) + self.dspin_pack_margin.setValue(self.conf.margin) + # + self.dspin_pack_spacing = QtWidgets.QSpinBox() + self.dspin_pack_spacing.setSingleStep(1) + self.dspin_pack_spacing.setWrapping(False) + self.dspin_pack_spacing.setValue(self.conf.spacing) + # + self.cbx_initial_scale_avg = QtWidgets.QCheckBox("Use 'Avarage' Initial Scale", self) + self.cbx_initial_scale_avg.setChecked(self.conf.initscaleavg) + # + self.cbx_layout_scaling = QtWidgets.QCheckBox("Layout: Auto Fit", self) + self.cbx_layout_scaling.setChecked(self.conf.autofit) + + pass + + # noinspection PyAttributeOutsideInit + def create_layouts(self): + main_layout = QtWidgets.QVBoxLayout(self) + + grp_layout = QtWidgets.QVBoxLayout() + grp_layout.addWidget(self.btn_run) + grp_layout.addWidget(self.btn_export) + grp_layout.addWidget(self.btn_import) + grp_layout.addWidget(self.cbx_use_link) + grp_layout.addWidget(self.cbx_fix_set_names) + #hor_layout = QtWidgets.QHBoxLayout() + grp_layout.addWidget(self.cbx_custom_path) + grp_layout.addWidget(self.field_custom_path) + #grp_layout.addLayout(hor_layout) + + # grp_layout.addWidget(self.btn_edit) + grp = QtWidgets.QGroupBox("UV Operations") + grp.setLayout(grp_layout) + main_layout.addWidget(grp) + + # Export Settings + grp_layout = QtWidgets.QVBoxLayout() + grp_layout.addWidget(self.cbx_keepuv) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(QtWidgets.QLabel("Scripts")) + hor_layout.addWidget(self.combo_scripts) + grp_layout.addLayout(hor_layout) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(QtWidgets.QLabel("Up Axis")) + hor_layout.addWidget(self.radioAxisY) + hor_layout.addWidget(self.radioAxisZ) + grp_layout.addLayout(hor_layout) + + grp = QtWidgets.QGroupBox("Export Settings") + grp.setLayout(grp_layout) + main_layout.addWidget(grp) + + # Utilites + grp_layout = QtWidgets.QVBoxLayout() + grp_layout.addWidget(self.btn_fix_shell_normals) + grp_layout.addWidget(self.btn_edit_settings) + # hor_layout = QtWidgets.QHBoxLayout() + # hor_layout.addWidget(self.btn_uvl_edges) + # grp_layout.addLayout(hor_layout) + + grp = QtWidgets.QGroupBox("Utilities") + grp.setLayout(grp_layout) + main_layout.addWidget(grp) + + # UV Packer + grp_layout = QtWidgets.QVBoxLayout() + grp = QtWidgets.QGroupBox("UV Packer") + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(self.btn_pack) + grp_layout.addLayout(hor_layout) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(QtWidgets.QLabel("UV Set to pack")) + hor_layout.addWidget(self.combo_pack_uvset) + grp_layout.addLayout(hor_layout) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(QtWidgets.QLabel("Packing Quality")) + hor_layout.addWidget(self.combo_pack_quality) + grp_layout.addLayout(hor_layout) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(QtWidgets.QLabel("Mutations")) + hor_layout.addWidget(self.dspin_pack_mutations) + hor_layout.addWidget(QtWidgets.QLabel("Resolution")) + hor_layout.addWidget(self.dspin_pack_resolution) + grp_layout.addLayout(hor_layout) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(QtWidgets.QLabel("Margin")) + hor_layout.addWidget(self.dspin_pack_margin) + hor_layout.addWidget(QtWidgets.QLabel("Spacing")) + hor_layout.addWidget(self.dspin_pack_spacing) + grp_layout.addLayout(hor_layout) + + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(self.cbx_initial_scale_avg) + grp_layout.addLayout(hor_layout) + hor_layout = QtWidgets.QHBoxLayout() + hor_layout.addWidget(self.cbx_layout_scaling) + grp_layout.addLayout(hor_layout) + + grp_layout.addStretch() + grp.setLayout(grp_layout) + main_layout.addWidget(grp) + pass + + def create_connections(self): + self.btn_run.clicked.connect(self.riz_run) + self.btn_export.clicked.connect(self.riz_export) + self.btn_import.clicked.connect(self.riz_import) + self.btn_fix_shell_normals.clicked.connect(fix_shell_border_normals) + self.btn_edit_settings.clicked.connect(self.browse_settings_location) + self.cbx_custom_path.stateChanged.connect(self.validate_export_path) + self.field_custom_path.textEdited.connect(self.validate_export_path) + + self.cbx_keepuv.stateChanged.connect(self.set_config) + self.slider_pack_uvmap.valueChanged.connect(self.ui_pack_update_labels) + + self.radioAxisY.clicked.connect(self.set_config) + self.radioAxisZ.clicked.connect(self.set_config) + # self.combo_scripts.currentIndexChanged.connect(self.ui_toggle_roundtrip_option) + + self.dspin_pack_mutations.valueChanged.connect(self.set_config) + self.dspin_pack_resolution.valueChanged.connect(self.set_config) + self.dspin_pack_margin.valueChanged.connect(self.set_config) + self.dspin_pack_spacing.valueChanged.connect(self.set_config) + self.cbx_layout_scaling.stateChanged.connect(self.set_config) + self.cbx_use_link.stateChanged.connect(self.set_config) + self.cbx_fix_set_names.stateChanged.connect(self.set_config) + + self.btn_pack.clicked.connect(self.riz_pack) + return + + def riz_run(self): + # Confirm application path + if not os.path.exists(self.conf.apppath): + self.manual_locate_rizom() + + if self.link and self.cbx_use_link.isChecked(): + self.port = self.link.RunRizomUV() + print(f"{PRNT_STRING} RizomUV {self.link.RizomUVVersion()} link established. Now listening to commands on TCP port: {str(self.port)}") + + # Enable UI relevant only for RizomUV Link + # self.btn_uvl_edges.setEnabled(True) + # self.btn_uvl_edges.setStyleSheet("background-color: {}; color: white".format("#ef4000")) + else: + # Use original method if link is not working. + print(PRNT_STRING, "RizomUV link not available. Communicating using LUA script file.") + cmd = '"' + self.conf.apppath + '" -cf "' + self.conf.rizom_script_path + '"' + print(cmd) + + if platform.system() == "Windows": + self.sp = subprocess.Popen(cmd, shell=True) + else: + self.sp = subprocess.Popen(["open", "-a", self.conf.apppath, "-cf", self.conf.rizom_script_path]) + + self.btn_export.setEnabled(True) + return + + def riz_pack(self): + # Export model with no script loaded, + # Also tell Rizom not to load the model in the riz_export function + # because it sometimes does not have time to load before we overwrite the lua file with these commands. + current_uv = self.combo_pack_uvset.currentText() + # current_uv_index = self.combo_pack_uvset.currentIndex() + + exported = self.riz_export(False, False) + if not exported: + return + + # Construct LUA code from GUI options + cmd = '' + # Common repeated text chunk + cmd_properties_prefix = 'ZomIslandGroups({Mode="SetGroupsProperties", WorkingSet="Visible", MergingPolicyString="A_ADD|AIB_ADD_A_VALUE_B|B_CLONE", GroupPaths={ "RootGroup" }, ' + + # Load model + cmd += 'ZomLoad({File={Path="'+self.conf.exportFile+'", ImportGroups=true, XYZUVW=true, UVWProps=true}})\n' + + # cmd += 'ZomUvset({Mode="SetCurrent", Name="Channel%s"})\n' % str(self.label_uvmap.text()) + cmd += 'ZomUvset({Mode="SetCurrent", Name="%s"})\n' % current_uv + # cmd += 'ZomSet({Path = "Vars.Viewport.ViewportUV.WorkingSet", Value = "Visible&Flat"})\n' + + # cmd += 'ZomSet({Path="Prefs.PackOptions.MapResolution", Value=%i})\n' % self.dspin_pack_resolution.value() + cmd += cmd_properties_prefix + 'Properties={Pack={Rotate={Step=90}}}})\n' + cmd += cmd_properties_prefix + 'Properties={Pack={Rotate={Mode=0}}}})\n' + + margin = float(self.dspin_pack_margin.value()) / self.dspin_pack_resolution.value() + spacing = float(self.dspin_pack_spacing.value()) / self.dspin_pack_resolution.value() + cmd += cmd_properties_prefix + 'Properties={Pack={SpacingSize=%f}}})\n' % spacing + cmd += cmd_properties_prefix + 'Properties={Pack={MarginSize=%f}}})\n' % margin + + quality = [128, 256, 512, 1024, 2048] + cmd += cmd_properties_prefix + 'Properties={Pack={Resolution=%i}}})\n' % quality[self.combo_pack_quality.currentIndex()] + cmd += cmd_properties_prefix + 'Properties={Pack={MaxMutations=%i}}})\n' % self.dspin_pack_mutations.value() + + init_scale = 0 + if self.cbx_initial_scale_avg.isChecked(): + init_scale = 2 + + layout_scale = 0 + if self.cbx_layout_scaling.isChecked(): + layout_scale = 2 + + cmd += 'ZomSet({Path="Prefs.PackOptions.__ScalingMode", Value=%i})\n' % init_scale + cmd += 'ZomSave({File={Path="c:/users/root/appdata/local/temp/MayaRizomExport.fbx", UVWProps=true}, __UpdateUIObjFileName=true})\n' + + cmd += 'ZomIslandGroups({Mode="DistributeInTilesEvenly", WorkingSet="Visible&Flat", MergingPolicyString="A_ADD|AIB_ADD_A_VALUE_B|B_CLONE", UseTileLocks=true, UseIslandLocks=true})\n' + cmd += 'ZomPack({RootGroup="RootGroup", WorkingSet="Visible&Flat", ProcessTileSelection=false, RecursionDepth=1, Translate=true, LayoutScalingMode=%i, Scaling={Mode=%i}})\n' % (layout_scale, init_scale) + + if DEBUG: + print(cmd) + self.write_to_lua_file(cmd) + return + + def fbx_export(self): + # FBX Export + if not cmds.pluginInfo("fbxmaya", loaded=True, query=True): + cmds.loadPlugin("fbxmaya") + + cmds.undoInfo(openChunk=True) + mel.eval('ConvertInstanceToObject;') # This is the only command that is important to undo. + mel.eval('FBXExportSmoothingGroups -v true;') + mel.eval('FBXExportTriangulate -v false;') + mel.eval('FBXExportSmoothMesh -v false;') + mel.eval('FBXExportUpAxis {};'.format(self.conf.upaxis)) + print('FBXExport -s -f "{}";'.format(self.conf.exportFile)) + mel.eval('FBXExport -s -f "{}";'.format(self.conf.exportFile)) + try: + cmds.undo() + except RuntimeError as RE: + # There are no more commands to undo + pass + + # End FBX Export + return + + def riz_export(self, use_script=True, load_model=True): + # displaySmoothness -divisionsU 0 + self.exported_objects = cmds.ls(selection=True, tr=True) + + # Exit preview smooth, because the FBXExportSmoothMesh option is not always respected by the exporter. + cmds.displaySmoothness(du=0, dv=0, pw=4, ps=0, po=1) + + if self.cbx_custom_path.isChecked() and not self.field_custom_path.text() == self.conf.custompath: + self.set_config() + + if self.link and self.cbx_use_link.isChecked(): + print("Rizom Link version", self.link.Version()) + + # FBX Export + self.fbx_export() + if not self.port: + self.riz_run() + + params = { + "File.Path": self.conf.exportFile, + "File.XYZUVW": True, # 3D + UV data loaded (use File.XYZ instead to load 3D data only) + "File.UVWProps": True, # UVs properties such as pinning, texel density settings etc... will be loaded + "File.ImportGroups": True, # Island group hierarchy will be loaded + "__Focus": True, # Focus viewports on the loaded mesh + # "Data.UseImportedUVWPolygons": False, + } + + self.link.Load(params) + + if DEBUG: print(f"{PRNT_STRING} EXPORTED TO:", self.conf.exportFile) + cmds.select(self.exported_objects) + + # Enable import button if it was disabled. + self.btn_import.setEnabled(True) + else: + # Classic Method # + print(f"{PRNT_STRING} Exporting model") + self.cleanse_namespaces() # Agressive cleanse + if not self.exported_objects: + return False + + # Delete history in case their are empty groups that would be removed by this action. + # Otherwise they would be cleared during import and we have an object count missmatch. + cmds.bakePartialHistory() + cmd = '' + + # Export Options + if load_model: + if self.cbx_keepuv.isChecked(): + cmd += 'ZomLoad({File={Path="'+self.conf.exportFile+'", ImportGroups=true, XYZUVW=true, UVWProps=true}})\n' + else: + cmd += 'ZomLoad({File={Path="'+self.conf.exportFile+'", ImportGroups=true, XYZ=true}, NormalizeUVW=true})\n' + + # Add scripts + if self.combo_scripts.currentIndex() and use_script is True: + script_name = scriptlist.scripts[self.combo_scripts.currentIndex()-1][0] + script_path = os.path.join(os.path.dirname(__file__), 'lua_scripts/%s' % script_name) + with open(script_path, 'r') as lua_script: + cmd += lua_script.read() + + if DEBUG: print(f"{PRNT_STRING} {cmd}") + + # FBX Export + self.fbx_export() + + if DEBUG: print(f"{PRNT_STRING} EXPORTED TO: {self.conf.exportFile}") + cmds.select(self.exported_objects) + + self.write_to_lua_file(cmd) + self.btn_import.setEnabled(True) + return True + + def riz_import(self): + if not os.path.exists(self.conf.exportFile): + om.MGlobal.displayError("Could not locate exported file: %s" % self.conf.exportFile) + self.btn_import.setEnabled(False) + + if self.cbx_use_link.isChecked(): + if self.link: + try: + self.link.Save({"File.Path": self.conf.exportFile}) + except RuntimeError as RE: + cmds.error('Could not send Save command to Rizom over Python Link. Was it enabled when you exported?') + + # FBX Import + namespace = ':RIZOMUV' + if not cmds.namespace(ex=namespace): + cmds.namespace(add=namespace) + cmds.namespace(set=namespace) + + mel.eval('FBXImportMode -v add;') + mel.eval('FBXImport -f "{}";'.format(self.conf.exportFile)) + # END FBX Import + + imported_objects = cmds.ls('RIZOMUV:*', long=True, type="transform") + original_matches = [] + for riz_obj in imported_objects: + print ("Imported object name", riz_obj) + original = riz_obj.replace('RIZOMUV:', '') + original_matches.append(original) + + # List UV sets of each item, skip if it has none. This is simply to exclude objects like group nodes. + original_uvsets = cmds.polyUVSet(original, allUVSets=True, q=True) + if not original_uvsets: + print(f"Object {riz_obj} has no UVSets. Will not attempt UV Transfer") + continue + + imported_uvsets = cmds.polyUVSet(riz_obj, allUVSets=True, q=True) + + if original_uvsets: + # Check names # + if self.cbx_fix_set_names.isChecked(): + for i in range(len(original_uvsets)): + try: + if not original_uvsets[i] == imported_uvsets[i]: + cmds.polyUVSet(riz_obj, rename=True, uvSet=imported_uvsets[i], newUVSet=original_uvsets[i]) + except IndexError: + # This can happen due to a weird Maya bug that I don't understand where the object has more + # uvSets listed with the polyUVSet comman, than it has in the UV Set Editor + print ("UV Set Index Error. Objects don't seem to have the same amount of UV sets") + print("Original UV Sets:", original_uvsets, original) + print("Imported UV Sets:", imported_uvsets, riz_obj) + pass + try: + #cmds.polyTransfer(original, ao=riz_obj, ch=False, uv=True) + cmds.polyTransfer(original, ao=riz_obj, ch=False, vc=False, v=False, uv=True) + except RuntimeError as rt: + print (" Could not transfer UVs from", riz_obj, "to", original) + if DEBUG: print (" Transfering UVs from", riz_obj, "to", original) + + cmds.select(original_matches) + cmds.bakePartialHistory() + cmds.delete(':RIZOMUV:*') + cmds.namespace(rm=':RIZOMUV') + + return + + def riz_link(self): + """ + Establishes link with RizomUV. Even though this is just a single line, I want it in a function to refresh UI. + """ + pass + + def browse_settings_location(self): + os.startfile(os.path.dirname(self.conf.config_file_path)) + + def ui_pack_update_labels(self): + print("Value changed") + self.label_uvmap.setText(str(self.slider_pack_uvmap.value())) + + def ui_update_uvchannels(self): + sel_obj = cmds.ls(sl=True, tr=True) + if not sel_obj: + self.combo_pack_uvset.clear() + return + + uvsets = cmds.polyUVSet(sel_obj[0], allUVSets=True, q=True) + current_set = cmds.polyUVSet(sel_obj[0], cuv=True, q=True) + if uvsets: + print (uvsets) + self.combo_pack_uvset.clear() + self.combo_pack_uvset.addItems(uvsets) + self.combo_pack_uvset.setCurrentIndex(uvsets.index(current_set[0])) + + def validate_export_path(self): + if self.cbx_custom_path.isChecked(): + if not os.path.exists(self.field_custom_path.text()): + self.btn_import.setEnabled(False) + self.btn_export.setEnabled(False) + self.field_custom_path.setStyleSheet("color: red") + return + else: + self.conf.exportFile = os.path.join(self.conf.custompath, self.conf.objname) + self.btn_import.setEnabled(True) + self.btn_export.setEnabled(True) + self.field_custom_path.setStyleSheet("color: white") + else: + self.conf.reset_export_path() + self.field_custom_path.setStyleSheet("color: grey") + self.btn_import.setEnabled(True) + self.btn_export.setEnabled(True) + + print(self.conf.exportFile) + + def cleanse_namespaces(self): + """ Step One -- + Try to delete all existing RIZOMUV namespaces until it fails, this should get rid of stacked instances like + RIZOMUV:RIZOMUV:RIZOMUV + """ + fail = False + while(not fail): + try: + cmds.namespace(rm=':RIZOMUV', mnr=True) + except: + fail = True + + """ Step Two -- + In one scene I got the namespaces had gotten renamed to RIZOMUV1, RIZOMUV2, RIZOMUV3 etc + so this finds any leftoverrs and deletes them. + """ + # existing_ns = cmds.namespaceInfo(listNamespace=True, listOnlyNamespaces=True) + # for entry in existing_ns: + # if 'RIZOMUV' in entry: + # cmds.namespace(rm=entry, mnr=True) + # + # return + + """ Step Two, updated -- + Let's try to remove other namespaces than simply RIZOM as having others can prevent the import from working + """ + c = cmds.listRelatives(ad=True) + if not c: + return + + for obj in c: + ns_split = obj.split(':') + if len(ns_split)>1: + result = cmds.confirmDialog(m="Selected objects have namespaces assigned. \n" + "These must go before you can use the tool correctly\n" + "Should I delete them for you? (Including on unselected objects)\n\n" + "Namespace on object: \n%s" % ns_split[0], button=['Remove', 'Cancel']) + if result == 'Remove': + cmds.namespace(rm=ns_split[0], mnr=True) + else: + return + return + + def write_to_lua_file(self, command): + with open(self.conf.rizom_script_path, 'w') as f: + f.write(command) + print(" Wrote command to lua file:", self.conf.rizom_script_path) + for line in command.split("\n"): + print("\t", line) + return + + def set_config(self): + self.conf.loaduvs = self.cbx_keepuv.isChecked() + + if self.radioAxisY.isChecked(): + self.conf.upaxis = "Y" + else: + self.conf.upaxis = "Z" + + self.conf.mutations = self.dspin_pack_mutations.value() + self.conf.resolution = self.dspin_pack_resolution.value() + self.conf.margin = self.dspin_pack_margin.value() + self.conf.spacing = self.dspin_pack_spacing.value() + self.conf.autofit = self.cbx_layout_scaling.isChecked() + self.conf.fixuvnames = self.cbx_fix_set_names.isChecked() + self.conf.useuvlink = self.cbx_use_link.isChecked() + self.conf.usecustompath = self.cbx_custom_path.isChecked() + self.conf.custompath = self.field_custom_path.text() + + self.conf.save_xml() + + def closeEvent(self, event): + self.conf.save_xml() + + +def GetMayaWidget(): + """ + Return the Maya main window widget as a Python object + """ + main_window_ptr = omui.MQtUtil.mainWindow() + if sys.version_info.major >= 3: + return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) + else: + return wrapInstance(long(main_window_ptr), QtWidgets.QWidget) + + +def fix_shell_border_normals(): + obj_list = cmds.ls(sl=True, o=True) + all_final_borders = [] + + for sub_obj in obj_list: + cmds.select(sub_obj, r=True) + + cmds.polyNormalPerVertex(ufn=True) + cmds.polySoftEdge(sub_obj, a=180, ch=1) + print("Soften all") + + # Select object UVs + cmds.select(sub_obj + '.map[*]') + mel.eval('polySelectBorderShell 1;') + uv_border = cmds.polyListComponentConversion(te=True, internal=True) + uv_border = cmds.ls(uv_border, fl=True) + final_border = [] + + # Magical filter + for curEdge in uv_border: + edge_uvs = cmds.polyListComponentConversion(curEdge, tuv=True) + edge_uvs = cmds.ls(edge_uvs, fl=True) + + if len(edge_uvs) > 2: + final_border.append(curEdge) + + cmds.polySoftEdge(final_border, a=0, ch=1) + all_final_borders.append(final_border) + + cmds.select(cl=True) + for sel_l in all_final_borders: + cmds.select(sel_l, add=True) + cmds.hilite(obj_list) + +def run(): + scriptJobs = cmds.scriptJob(listJobs=True) + for sj in scriptJobs: + if "RizomBridge" in sj: + print(PRNT_STRING, "Killing preexisting scriptJob:", sj) + cmds.scriptJob(kill=int(sj.split(':')[0])) + + d = RizomUVBridgeWindow() + d.show(dockable=True) + + +if __name__ == "__main__": + run() + diff --git a/Scripts/Modeling/UV/RizomBridge/__init__.py b/Scripts/Modeling/UV/RizomBridge/__init__.py new file mode 100644 index 0000000..5175b1d --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * diff --git a/Scripts/Modeling/UV/RizomBridge/lua_scripts/__init__.py b/Scripts/Modeling/UV/RizomBridge/lua_scripts/__init__.py new file mode 100644 index 0000000..3240337 --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/lua_scripts/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * \ No newline at end of file diff --git a/Scripts/Modeling/UV/RizomBridge/lua_scripts/blank.lua b/Scripts/Modeling/UV/RizomBridge/lua_scripts/blank.lua new file mode 100644 index 0000000..de03a3e --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/lua_scripts/blank.lua @@ -0,0 +1 @@ +ZomSet({Path="Prefs.FileSuffix", Value=""}) diff --git a/Scripts/Modeling/UV/RizomBridge/lua_scripts/box_algorithm.lua b/Scripts/Modeling/UV/RizomBridge/lua_scripts/box_algorithm.lua new file mode 100644 index 0000000..18e8517 --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/lua_scripts/box_algorithm.lua @@ -0,0 +1,6 @@ +ZomSelect({PrimType="Edge", WorkingSet="Visible", Select=true, ResetBefore=true, ProtectMapName="Protect", FilterIslandVisible=true, Auto={Box={ActiveEdges={ "XPYP", "XPZP", "XPYM", "XPZM", "YPZP", "YMZP", "YMZM", "YPZM", "XMYP", "XMZP", "XMYM", "XMZM" }}, HandleCutter=true, StoreCoordsUVW=true, FlatteningMode=0, FlatteningUnfoldParams={Iterations=1, BorderIntersections=true, TriangleFlips=true}}}) +ZomCut({PrimType="Edge", WorkingSet="Visible"}) +ZomLoad({Data={CoordsUVWInternalPath="Mesh.Tmp.AutoSelect.UVW"}}) +ZomIslandGroups({Mode="DistributeInTilesByBBox", WorkingSet="Visible", MergingPolicy=8322}) +ZomIslandGroups({Mode="DistributeInTilesEvenly", WorkingSet="Visible", MergingPolicy=8322, UseTileLocks=true, UseIslandLocks=true}) +ZomPack({ProcessTileSelection=false, RecursionDepth=1, RootGroup="RootGroup", WorkingSet="Visible", Scaling={Mode=0}, Rotate={}, Translate=true, LayoutScalingMode=2}) \ No newline at end of file diff --git a/Scripts/Modeling/UV/RizomBridge/lua_scripts/mosaic_algorithm.lua b/Scripts/Modeling/UV/RizomBridge/lua_scripts/mosaic_algorithm.lua new file mode 100644 index 0000000..fad2883 --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/lua_scripts/mosaic_algorithm.lua @@ -0,0 +1,10 @@ +var = ZomGet("Vars.AutoSelect.Mosaic.Developability") + +ZomSet({Path="Prefs.FileSuffix", Value=""}) +ZomSelect({PrimType="Edge", WorkingSet="Visible", Select=true, ResetBefore=true, ProtectMapName="Protect", FilterIslandVisible=true, Auto={QuasiDevelopable={Developability=var, IslandPolyNBMin=1, FitCones=false, Straighten=true}, HandleCutter=true, StoreCoordsUVW=true, FlatteningMode=0, FlatteningUnfoldParams={Iterations=1, BorderIntersections=true, TriangleFlips=true}}}) +ZomCut({PrimType="Edge", WorkingSet="Visible"}) +ZomLoad({Data={CoordsUVWInternalPath="Mesh.Tmp.AutoSelect.UVW"}}) +ZomIslandGroups({Mode="DistributeInTilesByBBox", WorkingSet="Visible", MergingPolicy=8322}) +ZomIslandGroups({Mode="DistributeInTilesEvenly", WorkingSet="Visible", MergingPolicy=8322, UseTileLocks=true, UseIslandLocks=true}) +ZomPack({ProcessTileSelection=false, RecursionDepth=1, RootGroup="RootGroup", WorkingSet="Visible", Scaling={Mode=2}, Rotate={}, Translate=true, LayoutScalingMode=2}) + diff --git a/Scripts/Modeling/UV/RizomBridge/lua_scripts/sharp_edge_algorithm.lua b/Scripts/Modeling/UV/RizomBridge/lua_scripts/sharp_edge_algorithm.lua new file mode 100644 index 0000000..d9523b8 --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/lua_scripts/sharp_edge_algorithm.lua @@ -0,0 +1,11 @@ +var = ZomGet("Vars.AutoSelect.SharpEdges.Angle") + +ZomSet({Path="Prefs.FileSuffix", Value=""}) +ZomSet({Path="Vars.EditMode.ElementMode", Value=1}) +ZomSelect({PrimType="Edge", WorkingSet="Visible", Select=true, All=true}) +ZomSelect({PrimType="Edge", WorkingSet="Visible", Select=true, ResetBefore=true, ProtectMapName="Protect", FilterIslandVisible=true, Auto={SharpEdges={AngleMin=var}, PipesCutter=false, HandleCutter=true, StoreCoordsUVW=true, FlatteningMode=0, FlatteningUnfoldParams={Iterations=1, BorderIntersections=true, TriangleFlips=true}}}) +ZomCut({PrimType="Edge", WorkingSet="Visible"}) +ZomLoad({Data={CoordsUVWInternalPath="Mesh.Tmp.AutoSelect.UVW"}}) +ZomIslandGroups({Mode="DistributeInTilesByBBox", WorkingSet="Visible", MergingPolicy=8322}) +ZomIslandGroups({Mode="DistributeInTilesEvenly", WorkingSet="Visible", MergingPolicy=8322, UseTileLocks=true, UseIslandLocks=true}) +ZomPack({ProcessTileSelection=false, RecursionDepth=1, RootGroup="RootGroup", WorkingSet="Visible", Scaling={Mode=2}, Rotate={}, Translate=true, LayoutScalingMode=2}) \ No newline at end of file diff --git a/Scripts/Modeling/UV/RizomBridge/rizom_icon.png b/Scripts/Modeling/UV/RizomBridge/rizom_icon.png new file mode 100644 index 0000000..a36b104 Binary files /dev/null and b/Scripts/Modeling/UV/RizomBridge/rizom_icon.png differ diff --git a/Scripts/Modeling/UV/RizomBridge/scriptlist.py b/Scripts/Modeling/UV/RizomBridge/scriptlist.py new file mode 100644 index 0000000..d885e9b --- /dev/null +++ b/Scripts/Modeling/UV/RizomBridge/scriptlist.py @@ -0,0 +1,2 @@ +# Edit this list when adding new scripts to the lua_script folder +scripts = [("box_algorithm.lua", "Box"), ("mosaic_algorithm.lua", "Mosaic"), ("pelt_algorithm.lua", "Pelt"), ("sharp_edge_algorithm.lua", "Sharp Edge")] \ No newline at end of file diff --git a/Scripts/Modeling/UV/UVDeluxe/__init__.py b/Scripts/Modeling/UV/UVDeluxe/__init__.py new file mode 100644 index 0000000..5175b1d --- /dev/null +++ b/Scripts/Modeling/UV/UVDeluxe/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * diff --git a/Scripts/Modeling/UV/UVDeluxe/config.uvd b/Scripts/Modeling/UV/UVDeluxe/config.uvd new file mode 100644 index 0000000..7b6aace Binary files /dev/null and b/Scripts/Modeling/UV/UVDeluxe/config.uvd differ diff --git a/Scripts/Modeling/UV/UVDeluxe/uistates.py b/Scripts/Modeling/UV/UVDeluxe/uistates.py new file mode 100644 index 0000000..1d1c4df --- /dev/null +++ b/Scripts/Modeling/UV/UVDeluxe/uistates.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pickle +import os +import maya.cmds as mc + +version = 120 + +class UiStates(): + file = 'config.uvd' + filepath = os.path.join(os.path.dirname(__file__), file) + + def __init__(self): + self.version = version + #Window + self.widthHeight = (1150,700) + self.collapseFrame0 = False + self.collapseFrame1 = True + self.collapseFrame2 = True + self.collapseFrame3 = True + self.collapseFrame4 = True + self.collapseFrame5 = True + self.collapseFrame6 = True + self.collapseFrame7 = True + self.collapseFrame8 = True + + #Settings + #self.textureSize = (5,5) + #self.forgetTextureSize = False + self.detectTextureSize = True + self.retainCS = mc.texMoveContext('texMoveContext',q=True,scr=True) + self.matchDist = 0.05 + + #Quicksnap + self.snapPath = mc.workspace(q=True,rd=True) + + @staticmethod + def pickleDump(uis): + with open(UiStates.filepath, 'wb') as datafile: + pickle.dump(uis, datafile) + + @staticmethod + def pickleLoad(): + if os.path.exists(UiStates.filepath): + print("%s found, loading settings." % UiStates.file) + try: + with open(UiStates.filepath, 'rb') as datafile: + uis = pickle.load(datafile) + except EOFError: + print("Warning: The file is empty or corrupted.") + os.remove(UiStates.filepath) + return UiStates() + except Exception as e: + print(f"Error loading settings: {e}") + os.remove(UiStates.filepath) + return UiStates() + try: + pickledVer = uis.version + if pickledVer < version: + os.remove(UiStates.filepath) + return UiStates() + except: + os.remove(UiStates.filepath) + return UiStates() + return uis + else: + return UiStates() + + + def setUiState(self): + #Window + self.widthHeight = mc.window('UVDeluxe',query=True,wh=True) + self.collapseFrame0 = mc.frameLayout('layout_Settings', query=True, cl=True) + self.collapseFrame1 = mc.frameLayout('layout_Mover', query=True, cl=True) + self.collapseFrame2 = mc.frameLayout('layout_Scaler', query=True, cl=True) + self.collapseFrame3 = mc.frameLayout('layout_Ratio', query=True, cl=True) + self.collapseFrame4 = mc.frameLayout('layout_Straighten', query=True, cl=True) + self.collapseFrame5 = mc.frameLayout('layout_Align', query=True, cl=True) + self.collapseFrame6 = mc.frameLayout('layout_QuickSnap', query=True, cl=True) + self.collapseFrame7 = mc.frameLayout('layout_MatchUV', query=True, cl=True) + self.collapseFrame8 = mc.frameLayout('layout_SelectionSets',query=True, cl=True) + + self.detectTextureSize = mc.checkBox ('DTR', query=True, v=True) + + self.retainCS = mc.texMoveContext('texMoveContext',q=True,scr=True) + #Qucksnap + self.snapPath = mc.textField("pathField",query=True,text=True) + ''' Dump ''' + UiStates.pickleDump(self) diff --git a/Scripts/Modeling/UV/UVDeluxe/uvdeluxe.py b/Scripts/Modeling/UV/UVDeluxe/uvdeluxe.py new file mode 100644 index 0000000..1a39a47 --- /dev/null +++ b/Scripts/Modeling/UV/UVDeluxe/uvdeluxe.py @@ -0,0 +1,1402 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#from distutils import command +#import subprocess +import maya.cmds as mc +import maya.mel as mel +import pymel.core as pm +import math +import os + +#Define globals +version = '1.1.2' +mult = 1 + +################# +###UI SETTINGS### +#vvvvvvvvvvvvvvv# +from .uistates import UiStates +uis = UiStates.pickleLoad() + +red = [0.807843,0.364706,0.305882] #[0.8, 0.3, 0.3] +green = [0.454902,0.752941,0.294118] #[0.3, 0.8, 0.3] +blue = [0.239216,0.615686,0.819608] +yellow = [0.819608,0.670588,0.239216] +################# + +#*****************# +# MAKE WINDOW # +#*****************# +def createUI(*args): + if mc.window('UVDeluxe', exists=True): + #remove when not in dev mode + mc.deleteUI('UVDeluxe') + + mc.window('UVDeluxe',s=True,width=440,title='UV Deluxe %s' % version, toolbox=False) + + #Create Scriptjobs + jobNumber1 = mc.scriptJob(parent='UVDeluxe',event=['linearUnitChanged',lambda *args:updateUI('units_text')]) + jobNumber2 = mc.scriptJob(parent='UVDeluxe',event=['SelectionChanged',lambda *args:updateUI_TexResAutomatic()]) + + ### Define texture panel ### + # Figure out percentage + pnSize = (220/float(uis.widthHeight[0]))*100 + + try: + #swp flag only available in 2011 and beyond + pane = mc.paneLayout('textureEditorPanel', paneSize=[1,pnSize,1], cn='vertical2', swp=1) + except: + pane = mc.paneLayout('textureEditorPanel', paneSize=[1,pnSize,1], cn='vertical2') + + uvTextureViews = mc.getPanel(scriptType='polyTexturePlacementPanel') + if len(uvTextureViews): + mc.scriptedPanel(uvTextureViews[0], e=True, unParent=True) + + #Load main ui elements# + mc.columnLayout ('MainColumn', columnWidth=220) + ui_Settings(0) + ui_Mover() + ui_Scaler() + ui_Ratio() + ui_MatchUV() + ui_Straighten() + ui_Align() + ui_SelectionSet() + ui_QuickSnap() + + #Add texture panel to window + mc.scriptedPanel(uvTextureViews[0], e=True, parent=pane) + + # SHOW WINDOW # + mc.showWindow('UVDeluxe') + mc.window('UVDeluxe',edit=True,mnb=True,widthHeight=uis.widthHeight) + updateUI_TexResAutomatic() + +#-----------------# +# UPDATE UI # +#-----------------# +def updateUI(command,*args): + if command=='units_text': + #'Maya Units' text in settings. + mc.text('unitText',edit=True,label='Working Units: %s' % mc.currentUnit(q=True,f=True)) + mc.text('ratio_unit',edit=True,label='Pixels per %s:' % mc.currentUnit(q=True)) + command = 'texture_res' + elif 'angle' in command: + # Straighten Edges Angle dials + if command.split(':')[1] == 'field': + aValue = mc.floatField('angleField',q=True,value=True) + mc.floatSlider('angleSlider',edit=True,value=aValue) + if command.split(':')[1] == 'slider': + aValue = mc.floatSlider('angleSlider',q=True,value=True) + mc.floatField('angleField',edit=True,value=aValue) + elif command == 'scale_pivot': + #Check if scaling pivot is based on selection + if mc.radioButton('PivSel',q=True,sl = True): + mc.floatField('scalePivotFieldU',edit=True,enable=False) + mc.floatField('scalePivotFieldV',edit=True,enable=False) + mc.text('spu',edit=True,enable=False) + mc.text('spv',edit=True,enable=False) + mc.button('samplePivotButton',edit=True,enable=False) + else: + mc.floatField('scalePivotFieldU',edit=True,enable=True) + mc.floatField('scalePivotFieldV',edit=True,enable=True) + mc.text('spu',edit=True,enable=True) + mc.text('spv',edit=True,enable=True) + mc.button('samplePivotButton',edit=True,enable=True) + return + +## Relevant for Texture Resolution ## +def updateUI_SetRatio(): + #Updates the floatField containing the value that gets queried by unfold. + mult = setRatioMultiplier() + unfold=0.0009765625*(mc.intField('densityField',q=True,value=True)) + mc.floatField('ratioField',edit=True,v=unfold*mult) + return + +def isPowerOfTwo(x): + return (x != 0) and ((x & (x - 1)) == 0) + +def updateUI_TexResAutomatic(): + checkbox = mc.checkBox('DTR',q=True,v=True) + if checkbox: + res = getFileResolution() + if res: + mc.text('resTextW', edit=True, label=res[0]) + mc.text('resTextH', edit=True, label=res[1]) + + # Update the resolution text of the manual controls. + # if isPowerOfTwo(res[0]) and res[0] >= 32 and res[0] <= 8192: + # mc.text('resTextManW', edit=True, label=res[0]) + # mc.text('resTextManH', edit=True, label=res[1]) + + updateUI_SetRatio() + return + +#----------------# +# Settings # +#----------------# +def ui_Settings(flag,*args): + def retainCompSpace(): + if mc.checkBox ('SCR',q=True,value=True): + mc.texMoveContext('texMoveContext',e=True,scr=True) + else: mc.texMoveContext('texMoveContext',e=True,scr=False) + uis.setUiState() + + def openPrefs(*args): + #Open preference window + mel.eval('preferencesWnd "general";') + #Change tab + mel.eval('textScrollList -edit -selectItem (uiRes("m_preferencesWnd.kSettingsTab")) prefIndex;') + mel.eval('switchPrefTabs 0;') + return + + def setResolution(*args): + # IMPORTANT! SetRatio: multiplier = ((multiplier*(8192/textureResW))/8) + iterations = 8 + resMin = 32 + #maxRes = 32*(2**iterations) + + #mel. Create a local var "sliderValue" to whatever textureSliderW is set to. + sliderValueW = mc.intSlider('textureSliderW', query=True, value=True) + sliderValueH = mc.intSlider('textureSliderH', query=True, value=True) + + #Width + p2 = resMin + for i in range(0,iterations+1,1): + if sliderValueW == i: + mc.text('resTextManW',edit=True,label=str(p2)) + p2*=2 + + #Height + p2 = resMin + for i in range(0,iterations+1,1): + if sliderValueH == i: + mc.text('resTextManH',edit=True,label=str(p2)) + p2*=2 + + return + + def callSetResAndUpdateUI(): + """ + Called when moving either resolution slider + """ + setResolution() + updateUI_SetRatio() + uis.setUiState() + return + + def checkDetectTextureResolution(): + """ + Called when checkbox value is changed for Detect Resolution + """ + if mc.checkBox('DTR',q=True,v=True): + mc.intSlider('textureSliderW', e=True, enable=False) + mc.intSlider('textureSliderH', e=True, enable=False) + mc.text('resTextManW', e=True, enable=False) + mc.text('resTextManH', e=True, enable=False) + else: + mc.intSlider('textureSliderW', e=True, enable=True) + mc.intSlider('textureSliderH', e=True, enable=True) + mc.text('resTextManW', e=True, enable=True) + mc.text('resTextManH', e=True, enable=True) + + uis.setUiState() + return + + mc.frameLayout('layout_Settings',label='Settings',width=220, + cll=True, + cl=uis.collapseFrame0, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + + ## Units and Preferences button + mc.columnLayout() + mc.rowLayout(numberOfColumns=2,cw2=(146,60)) + mc.text('unitText',label='Working Units: %s' % mc.currentUnit(q=True,f=True)) + mc.button(label='Maya Prefs',w=68,c=openPrefs) + mc.setParent('..') + + ## DeTect Resolution + s = [110,40,40] + mc.rowLayout(numberOfColumns=3,cw3=s) + mc.checkBox ('DTR',label="Detect Resolution:",h=20, + value = uis.detectTextureSize, cc=lambda *args:checkDetectTextureResolution()) + mc.text('resTextW',label='0') + mc.text('resTextH',label='0') + mc.setParent('..') + + ## Width controller + MAN = not uis.detectTextureSize + mc.rowLayout(numberOfColumns=3,cw3=(40,30,140)) + mc.text(label='Size W:') + mc.text('resTextManW', label='', enable = MAN) + mc.intSlider('textureSliderW' ,w=140, min=0, max=8, step=1, + enable= not uis.detectTextureSize, + value = 4, + cc=lambda *args:callSetResAndUpdateUI()) + mc.setParent('..') + + ## Height controller + mc.rowLayout(numberOfColumns=3,cw3=(40,30,140)) + mc.text(label='Size H:') + mc.text('resTextManH', label='', enable = MAN) + mc.intSlider('textureSliderH', w=140, min=0, max=8, step=1, + enable= not uis.detectTextureSize%(1+1), + value = 4, + cc=lambda *args:callSetResAndUpdateUI()) + mc.setParent('..') + + ## Create Checkboxes + mc.checkBox ('SCR',label='Retain component spacing (Move tool)',h=20, + value=uis.retainCS,cc=lambda *args:retainCompSpace()) + + ## Set parent for frameLayout & columnLayout + mc.setParent('..') + mc.setParent('..') + setResolution() + +#--------------# +# MOVER # +#--------------# +def ui_Mover(): + def move(dTuple,*args): + if not mc.polyListComponentConversion(fuv=True): + mel.eval('warning "Please select some UVs"') + else: + steps = mc.floatField('steps',q=True,v=True) + sel = mc.ls(sl = True) + mel.eval('polySelectBorderShell 0') + mc.polyEditUV(u=(steps*dTuple[0]),v=(steps*dTuple[1])) + #Refocus selection + mc.select(sel,r=True) + return + + mc.frameLayout('layout_Mover',label='Mover',width=220,cll=True,cl=uis.collapseFrame1, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + mc.columnLayout() + ## + mc.rowLayout(numberOfColumns=2,cw2=(107,107)) + mc.text(label='Move by steps of:') + mc.floatField('steps',precision=2,width=107,value=1.0) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=4,cw4=(52,52,52,52)) + mc.button(label='Up',w=52, c=lambda *args:move((0,1))) + mc.button(label='Down',w=52, c=lambda *args:move((0,-1))) + mc.button(label='Left',w=52, c=lambda *args:move((-1,0))) + mc.button(label='Right',w=52, c=lambda *args:move((1,0))) + mc.setParent('..') + mc.setParent('..') + mc.setParent('..') + +#-----------------------# +# SCALE AND ROTATE # +#-----------------------# +def ui_Scaler(): + def getPivot(method): + ## Methods + ## 1 = Updates UI with sampled position + ## 2 = Return Custom UV + ## None = return selection center + + try: + pivot = mc.polyEvaluate(mc.ls(sl = True), bc2=True) + pivotU=((pivot[0][0] + pivot[0][1]) * 0.5) + pivotV=((pivot[1][0] + pivot[1][1]) * 0.5) + except: + mel.eval("warning (\"You do not have any UVs selected!\")") + + if method == 1: + mc.floatField('scalePivotFieldU', edit=True, value=pivotU) + mc.floatField('scalePivotFieldV', edit=True, value=pivotV) + return + elif method == 2: + pivotU = mc.floatField('scalePivotFieldU', query=True, value=True) + pivotV = mc.floatField('scalePivotFieldV', query=True, value=True) + + return (pivotU, pivotV) + + def scale(button_info, *args): + button_data = button_info.split(':') + if button_data[0] == 'custom': + if button_data[1] == 'u': + button_data[0] = float(mc.floatField('scaleCustomU', query=True, v=True)) + else: + button_data[0] = float(mc.floatField('scaleCustomV', query=True, v=True)) + else: + button_data[0] = float(button_data[0]) + + #Get pivot from preset or selection + if mc.radioButton('CustomPivot', query=True, sl = True): pivotUV = getPivot(2) + else: pivotUV = getPivot(None) + + if button_data[1] == 'u': + mc.polyEditUV(pu=pivotUV[0], pv=pivotUV[1], su=button_data[0], sv=0) + if button_data[1] == 'v': + mc.polyEditUV(pu=pivotUV[0], pv=pivotUV[1], su=0, sv=button_data[0]) + return + + def smartRotate(dir): + if not mc.ls(sl = True): + mel.eval("print 'Could not rotate UVs: Nothing selected'") + return + + + resolution = getWorkingResolution() + resW = resolution[0] + resH = resolution[1] + + selCenter = getPivot(None) + #Get pivot from preset or selection + if mc.radioButton('CustomPivot', query=True, sl = True): pivotUV = getPivot(2) + else: pivotUV = selCenter + + #Correct texture/scaling ratio before rotating. + scaleV = resH/resW + mc.polyEditUV(pu=selCenter[0], pv=selCenter[1], su=0, sv=scaleV) + + #Perform rotate + mc.polyEditUV(pu=pivotUV[0], pv=pivotUV[1], angle=mc.intField('rot_deg', query=True, v=True)*dir) + + #Reverse correction + selCenter = getPivot(None) #Update position after potential move + scaleV = resW/resH + mc.polyEditUV(pu=selCenter[0], pv=selCenter[1], su=0, sv=scaleV) + return + + mc.frameLayout('layout_Scaler',label='Scaling and Rotation',width=220,cll=True,cl=uis.collapseFrame2, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + + mc.columnLayout() + #mc.text(label='Set scaling pivot to selection or custom cords',height=20) + mc.text(label='Pivot for scaling and roation:',height=20) + mc.radioCollection('SP_RC') + ## + mc.rowLayout(numberOfColumns=4,cw4=(65,75,34,34)) + mc.radioButton('PivSel',w=65,label='Selection',cl='SP_RC',onc=lambda *args:updateUI('scale_pivot'),h=14) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=3,cw3=(115,33,110)) + mc.radioButton('CustomPivot', h=18, w=80,label='Custom UV',cl='SP_RC',onc=lambda *args:updateUI('scale_pivot')) + mc.text('spu',l='Pos U:',align="left",w=33,enable=False) + mc.floatField('scalePivotFieldU',pre=7,w=60,v=0.0,h=18) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=3,cw3=(115,33,110)) + mc.button('samplePivotButton',l='Sample selection', c=lambda *args:getPivot(1),h=18,w=108) + mc.text('spv',l='Pos V:',align="left",w=33,enable=False) + mc.floatField('scalePivotFieldV',pre=7,w=60,v=0.0,h=18) + mc.setParent('..') + ## + + ## + mc.radioCollection('SP_RC',edit=True,select='PivSel') #Set default selection pivot mode + mc.text(label='Scale by:',height=20) + ## + mc.rowLayout(numberOfColumns=6,cw6=(35,25,25,30,42,47)) + mc.text(l='Width:',h=18) + mc.button(l='0.5',w=25, c=lambda *args:scale('0.5:u'),h=20, bgc=red) + mc.button(l='2.0',w=25, c=lambda *args:scale('2.0:u'),h=20, bgc=red) + mc.button(l='-1.0',w=27, c=lambda *args:scale('-1.0:u'),h=20, bgc=red) + mc.floatField('scaleCustomU',pre=3, v=0.001 ,w=42,h=20) + mc.button(l='Custom',w=47, c=lambda *args:scale('custom:u'),h=20, bgc=red) + mc.setParent('..') + ### + mc.rowLayout(numberOfColumns=6,cw6=(35,25,25,30,42,47)) + mc.text(l='Height:',h=18) + mc.button(l='0.5',w=25, c=lambda *args:scale('0.5:v'),h=20,bgc=green) + mc.button(l='2.0',w=25, c=lambda *args:scale('2.0:v'),h=20,bgc=green) + mc.button(l='-1.0',w=27, c=lambda *args:scale('-1.0:v'),h=20,bgc=green) + mc.floatField('scaleCustomV',pre=3, v=0.001, w=42,h=20) + mc.button(l='Custom',w=47, c=lambda *args:scale('custom:v'),h=20,bgc=green) + mc.setParent('..') + + ## + mc.text('') + mc.text(label='Smart Rotate: Maintain width/height ratio') + mc.rowLayout(numberOfColumns=3,cw3=(34,89,89)) + mc.intField('rot_deg',min=0,max=360,v=90,w=34) + mc.button('CW_BTN', label='Rotate CW',w=87, c=lambda *args:(smartRotate(-1))) + mc.button('CCW_BTN',label='Rotate CCW',w=89, c=lambda *args:(smartRotate(1))) + mc.setParent('..') + #mc.text(align='left',label=' (Rotates with width/height ratio correction)') + ## + mc.setParent('..') + mc.setParent('..') + return + +#----------------# +# RATIO UI # +#----------------# +def ui_Ratio(): + global mult + RS = RatioSampler() + + def ratioHelp(*args): + help =['This tool will scale your selected shells as close as possible to \nthe desired pixel density. Accuracy will vary depending on the \namount of distortion to your UVs. A new or unmodifed polyCube \nwill have no distortion and be scaled correctly.', + "Shells are scaled using the Unfold tool's -scale flag. \nThe output value is simply the number parsed to the \nUnfold tool, and all other options turned off"] + mc.confirmDialog(title='Set Ratio',button='Ok, whatever',message=str(help[0])+'\n\n'+str(help[1])); + return + + def sampleRatio(*args): + RS.getSource() + mc.button('SMR_BTN', e=True, enable=True) + return + + def setManRatio(*args): + RS.setRatio() + return + + def updateSourceRatio(*args): + RS.sourceRatio = mc.floatField('sampledRatioField', q=True, v=True) + print(RS.sourceRatio) + return + + def setRatio(*args): + if mc.checkBox('DTR',q=True,v=True): + resW = float(mc.text('resTextW',q=True,l=True)) + resH = float(mc.text('resTextH',q=True,l=True)) + else: + resW = float(mc.text('resTextManW',q=True,l=True)) + resH = float(mc.text('resTextManH',q=True,l=True)) + + #mult + #mult = setRatioMultiplier() + + #Define selection and convert it to uvs + selection = mc.ls(sl = True) + selection = mc.polyListComponentConversion(selection, tuv=True) + + #Find center of selection + pivot = mc.polyEvaluate(selection,bc2=True) + pu=((pivot[0][0] + pivot[0][1]) * 0.5) + pv=((pivot[1][0] + pivot[1][1]) * 0.5) + + #Correct texture/scaling ratio before rotating. + if resH != resW: + scaleV = resH/resW + mc.polyEditUV(pu=pu,pv=pv,su=0,sv=scaleV) + + mc.unfold(i=0,us=True,s=mc.floatField('ratioField',q=True,v=True)) + + #Reverse correction + scaleV = resW/resH + mc.polyEditUV(pu=pu,pv=pv,su=0,sv=scaleV) + else: + mc.unfold(i=0,us=True,s=mc.floatField('ratioField',q=True,v=True)) + + mc.select(selection,replace=True) + return + + mc.frameLayout('layout_Ratio',label='Ratio (Pixel density)',width=220,cll=True,cl=uis.collapseFrame3, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + mc.columnLayout() + ## + mc.rowLayout(numberOfColumns=2,cw2=(170,42)) + mc.text(label='Scale shells to desired pixel density',w=170) + mc.button(w=42,label='Help', align='right', command=ratioHelp) + mc.setParent('..') + + ##//Sampled Density Controls// + ## + s = 109,103 + mc.rowLayout(numberOfColumns=2,cw2=s) + mc.button(label='Copy Ratio', w=s[0], command=sampleRatio) + mc.button('SMR_BTN', w=s[1],label='Paste Ratio',command=setManRatio, enable=True) + mc.setParent('..') + + ## + mc.rowLayout(numberOfColumns=2,cw2=(108,106)) + mc.text(label='Custom Ratio:',w=108) + mc.floatField('sampledRatioField', pre=5, enable=True, w=106, v=0, cc=updateSourceRatio) + mc.setParent('..') + + ##//Pixel Density Controls// + ## + mc.text(label='') + s = [72,35,103] + mc.rowLayout(numberOfColumns=3,cw3=s) + mc.text('ratio_unit', w=s[0], label='Pixels per %s:' % mc.currentUnit(q=True)) + mc.intField('densityField',v=256,min=0,max=8192,w=s[1],cc=lambda *args:updateUI_SetRatio()) + mc.button(label='Set Pixel Density', w=s[2],command=setRatio) + mc.setParent('..') + + ## + mc.rowLayout(numberOfColumns=2,cw2=(108,106)) + mc.text(label='Pixel Density:',w=108) + #(ignore) + mc.floatField('ratioField',pre=5,enable=False,w=106, v=0.0009765625*(mc.intField('densityField',q=True,v=True))) + mc.setParent('..') + + ## + mc.setParent('..') + mc.setParent('..') + updateUI_SetRatio() + +#-------------------# +# MatchUV UI # +#-------------------# +def ui_MatchUV(): + def matchUVS(*args): + maxDist = uis.matchDist + def sortDist(tuple): + return tuple[1] + + def getOtherUVS(allSelected,suvs): + objects = [i.split('.')[0] for i in allSelected] + objects = [i for i in set(objects)] + uvs = mc.ls(mc.polyListComponentConversion(objects,tuv=True),fl=True) + return sorted(set.difference(set(uvs)-set(suvs))) + + ### SETUP ### + allSelected = mc.ls(sl = True) + # Create list of uvs + suvs = mc.ls(mc.polyListComponentConversion(tuv=True),sl = True,fl=True) + ouvs= getOtherUVS(allSelected,suvs) + + # Create list of positions + spos = [mc.polyEditUV(i,query=True) for i in suvs] + opos = [mc.polyEditUV(i,query=True) for i in ouvs] + + ### PERFORM ### + for i in range(len(suvs)): + withinRange = [] + for j in range(len(ouvs)): + x = spos[i][0]-opos[j][0] + y = spos[i][1]-opos[j][1] + dist = math.sqrt((x**2) + (y**2)) + if dist < maxDist: + withinRange.append((opos[j],dist)) + + withinRange = sorted(withinRange,key=sortDist) + if len(withinRange): + mc.polyEditUV(suvs[i],u=withinRange[0][0][0],v=withinRange[0][0][1],relative=False) + + uis.setUiState() + return + + def matchUVS_refresh_ui(*args): + mc.floatField(matchField, edit=True, value=mc.floatSlider(matchSlider, q=True, v=True)) + uis.matchDist = mc.floatField(matchField, query=True, value=True) + + + mc.frameLayout('layout_MatchUV',label='Match UVs',width=220,cll=True,cl=uis.collapseFrame7, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + + mc.columnLayout() + ## + mc.text(label='Snap selected UVs to closest unselected UVs') + mc.rowLayout(numberOfColumns=3,cw3=(70,60,80)) + mc.text(label="Max distance:") + matchField = mc.floatField(pre=5,v=uis.matchDist,w=50) + matchSlider = mc.floatSlider(min=0.0,max=1.0,value=uis.matchDist,w=82,dc=matchUVS_refresh_ui) + mc.setParent('..') + mc.button(width=80,label='Match UVs', command=matchUVS) + mc.setParent('..') + mc.setParent('..') + return + +#-------------------# +# Straighten UI # +#-------------------# + +def ui_Straighten(): + def help(*args): + help =['This script will flatten edges based on the initial angle of each edge.\nWhat that means is, if an edge is closer to being more vertical than \nhorizontal it can only be flattened vertically, or vice versa.', + 'So before the script flattens anything, it sorts all edges into two lists\nof horizontal and vertical before it decides which lists to flatten.'] + mc.confirmDialog(title='Straighten Edges',button='Got it!',message=help[0]+'\n\n'+help[1]) + return + ## UI ## + + mc.frameLayout('layout_Straighten',label='Straighten Edges',width=220,cll=True,cl=uis.collapseFrame4, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + mc.columnLayout() + ## + mc.rowLayout(numberOfColumns=2,cw2=(170,33)) + mc.text(label='Straighten based on UV selection') + mc.button(w=33,label='Info',align='right',c=help) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=3,cw3=(82,35,100)) + mc.text(label='Angle tolerance:',align='left',w=82) + mc.floatField('angleField',min=0,max=45,value=30,pre=1,w=35,cc=lambda *args:updateUI('angle:field')) + mc.floatSlider('angleSlider',min=0,max=45,value=30,w=82,dc=lambda *args:updateUI('angle:slider')) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=3,cw3=(71,71,71)) + mc.button(label='Horizontal',w=71, h=20, c=lambda *args:(straightenEdges('hori')), bgc=green) + mc.button(label='Vertical',w=71, h=20, c=lambda *args:(straightenEdges('vert')), bgc=red) + mc.button(label='Both',w=71, h=20, c=lambda *args:(straightenEdges('both')), bgc=yellow) + mc.setParent('..') + ## + mc.setParent('..') + mc.setParent('..') + +#-------------------# +# Align Tools UI # +#-------------------# + +def ui_Align(): + + mc.frameLayout('layout_Align',label='Align Tools',width=220,cll=True,cl=uis.collapseFrame5, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + mc.columnLayout() + ## + mc.rowLayout(numberOfColumns=2,cw2=(137,77)) + mc.text(align='left',label='Straighten shells by rotation:',w=137) + mc.button(label='Rotate Align',w=77, c=lambda *args:(align('--shell'))) + mc.setParent('..') + ## + ## + mc.text(label='Align Selected Shells:') + mc.rowLayout(numberOfColumns=4,cw4=(52,52,52,52)) + mc.button(label='Top', w=52, c=lambda *args:alignShells("up"), h=20, bgc=green) + mc.button(label='Bottom', w=52, c=lambda *args:alignShells("down"), h=20, bgc=green) + mc.button(label='Left', w=52, c=lambda *args:alignShells("left"), h=20, bgc=red) + mc.button(label='Right', w=52, c=lambda *args:alignShells("right"), h=20, bgc=red) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=2,cw2=(106,106)) + mc.button(label='Center Vertical', w=106, c=lambda *args:alignShells("centerV"), h=20, bgc=green) + mc.button(label='Center Horizontal', w=106, c=lambda *args:alignShells("centerH"), h=20, bgc=red) + mc.setParent('..') + ## + mc.rowLayout(numberOfColumns=1, w=212) + mc.button(label='Move Shells to 0-1 Space', w=212, c= gatherShells, h=20, bgc=yellow) + mc.setParent('..') + ## + mc.setParent('..') + mc.setParent('..') + return + +#-----------------------# +# SELECTION SETS # +#-----------------------# + +def ui_SelectionSet(): + selectionSlots = ['none','none','none','none','none','none'] + + colorBlank = [0.3,0.3,0.3] + uColor = [0.584314,0.772549,0.0] + vColor = [0.913725,0.878431,0.0] + eColor = [0.870588,0.643137,0.117647] + oColor = [0.152941,0.729412,0.447059] + fColor = [0.478431,0.227451,0.227451] + + def storeLoadSelection(slot): + #Store selection if button is blank.. + slotLabel = mc.button("slotButton%i" % slot,q=True,label=True) + if slotLabel == '': + sel = mc.ls(sl = True) + if len(sel): + selectionSlots[slot-1] = sel + + #fluff + try: + type = mc.ls(sl = True,fl=True)[0].split('.')[1] + type = type.split('[')[0] + except: + type = '' + + if type == 'map': + color = uColor + elif type == 'e': + color = eColor + elif type == 'vtx' or type == 'vtxFace': + color = vColor + elif type == 'f': + color = fColor + else: + color = oColor + + mc.button("slotButton%i" % slot,edit=True,label='%i' % len(mc.ls(sl = True,fl=True)),bgc=color) + #..load selection if not + else: + mc.select(selectionSlots[slot-1],replace=True) + + def clearSlot(slot): + mc.button("slotButton%i" % slot,edit=True,label='',bgc=colorBlank) + + mc.frameLayout('layout_SelectionSets',label='Selections',width=220,cll=True,cl=uis.collapseFrame8, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + mc.columnLayout() + mc.button("selectBorderEdges", label='Select Shell Border Edges', w=214, c=lambda *args: selectShellBorderEdges()) + mc.text(l=' Store selections') + mc.rowLayout(numberOfColumns=6) + mc.button("slotButton1", label='', w=49, c=lambda *args: storeLoadSelection(1), bgc=colorBlank) + mc.iconTextButton("slotTrash1",style="iconOnly", ann="Clear Slot 1", image="SP_TrashIcon.png", c=lambda *args: clearSlot(1)) + mc.button("slotButton2", label='', w=49, c=lambda *args: storeLoadSelection(2), bgc=colorBlank) + mc.iconTextButton("slotTrash2",style="iconOnly", ann="Clear Slot 2", image="SP_TrashIcon.png", c=lambda *args: clearSlot(2)) + mc.button("slotButton3", label='', w=49, c=lambda *args: storeLoadSelection(3), bgc=colorBlank) + mc.iconTextButton("slotTrash3",style="iconOnly", ann="Clear Slot 3", image="SP_TrashIcon.png", c=lambda *args: clearSlot(3)) + mc.setParent('..') + mc.rowLayout(numberOfColumns=6) + mc.button("slotButton4", label='', w=49, c=lambda *args: storeLoadSelection(4), bgc=colorBlank) + mc.iconTextButton("slotTrash4",style="iconOnly", ann="Clear Slot 4", image="SP_TrashIcon.png", c=lambda *args: clearSlot(4)) + mc.button("slotButton5" ,label='', w=49, c=lambda *args: storeLoadSelection(5), bgc=colorBlank) + mc.iconTextButton("slotTrash5",style="iconOnly", ann="Clear Slot 5", image="SP_TrashIcon.png", c=lambda *args: clearSlot(5)) + mc.button("slotButton6",label='', w=49, c=lambda *args: storeLoadSelection(6), bgc=colorBlank) + mc.iconTextButton("slotTrash6",style="iconOnly", ann="Clear Slot 6", image="SP_TrashIcon.png", c=lambda *args: clearSlot(6)) + mc.setParent('..') + mc.setParent('..') + mc.setParent('..') + +#--------------------# +# Quck UvSnapshot UI # +#--------------------# +def ui_QuickSnap(): + #path = os.path.expanduser('~') + #path = os.getenv('USERPROFILE') or os.getenv('HOME') + path = uis.snapPath + + def performQuicksnap(): + # options # + resolution = getWorkingResolution() + width = resolution[0] + height = resolution[1] + + if mc.checkBox ('doubleRes', query=True, value=True): + width *= 2 + height *= 2 + autoLoad = mc.checkBox ('autoLoad', query=True, value=True) + antiAlias = mc.checkBox ('antiAliasing', query=True, value=True) + + fileName = 'outUV' + format = mc.optionMenuGrp('fileFormat', query=True, value=True) + filePath = mc.textField('pathField',query=True,text=True) + '/' + fileName + '.' + format + uis.setUiState() + + # # # Perform # # # + mc.uvSnapshot(name=filePath,xr=width,yr=height,o=True,aa=antiAlias,ff=format) + + if autoLoad: + os.startfile(filePath) + + mc.frameLayout('layout_QuickSnap',label='Quick UV Snapshot',width=220,cll=True,cl=uis.collapseFrame6, + cc=lambda *args:uis.setUiState(), + ec=lambda *args:uis.setUiState()) + mc.columnLayout() + ## + mc.text(align='left',label='Create UV Snapshot for selected objects') + ## + mc.rowLayout(numberOfColumns=3,cw3=(65,65,70)) + mc.checkBox ('autoLoad',label='Open file',value=True) + mc.checkBox ('antiAliasing',label='Anti Alias',value=True) + mc.checkBox ('doubleRes',label='Double Size',value=False) + mc.setParent('..') + ## + #mc.text(label="Output directory:") + mc.textField("pathField",text=path,width=214) + ## + mc.rowLayout(numberOfColumns=2,cw2=(89,122)) + mc.optionMenuGrp('fileFormat',label="Format:",cat=[1,'left',0],cw2=[40,48]) + mc.button(label='Save snapshot',w=119, c=lambda *args:performQuicksnap()) + mc.menuItem(label='tga') + mc.menuItem(label='tif') + mc.menuItem(label='png') + mc.setParent('..') + ## + mc.setParent('..') + mc.setParent('..') + +#####////////////////////////////////##### +##### ##### +##### CLASSES ##### +##### ##### +#####////////////////////////////////##### + +class RatioSampler: + def __init__(self): + self.sourceArea = None + self.sourceUVArea = None + self.sourceRatio = None + def getSource(self): + if not len(pm.ls(sl=True)): return + self.sourceArea = self.__getAreaAverage() + self.sourceUVArea = self.__getUVAreaAverage() + self.sourceRatio = self.sourceArea/self.sourceUVArea + + mc.floatField('sampledRatioField', edit=True, v=self.sourceRatio) + return + + def setRatio(self): + selected = UVClass() + selected.getShells() + + for shell in selected.shells: + pm.select(shell.uvs) + targetArea = self.__getAreaAverage() + targetUVArea = self.__getUVAreaAverage() + targetRatio = targetArea/targetUVArea + + ratio = (targetRatio/self.sourceRatio)**0.5 + pivot = shell.getPivot() + + pm.polyMoveUV(s=[ratio, ratio], pvt=[pivot[0], pivot[1]], ch=False) + return + + def __getAreaAverage(self): + """ + Returns the average area of the selected faces (or corresponding faces). + The script pretends that the user is working in cm, or else the math doesn't work: + A 1x1cm plane will return an area of 1, and a 1x1m plane will return an area of 10000, + so the area of objects in meter will be multiplied by 0.0001 + """ + sel = pm.ls(sl=True) + + faces = pm.ls(pm.polyListComponentConversion(sel, tf=True), fl=True) + + # Get multiplier + unit = pm.currentUnit(q=True, f=True) + mult = 1.0 + if not unit == 'centimeter': + if unit == 'meter': + mult = 0.0001 + elif unit == 'millimeter': + mult = 1000 + + # Get area of faces + areas = [f.getArea(space='world') for f in faces] + sum = 0 + for a in areas: + sum += a * mult + + print("Face Area: ", sum/len(areas)) + return sum/len(areas) + + def __getUVAreaAverage(self): + """ + Returns the average area of the selected UVs. + """ + sel = pm.ls(sl=True) + + faces = pm.ls(pm.polyListComponentConversion(sel, tf=True), fl=True) + + #Get area of UV Faces + UVAreas = [f.getUVArea() for f in faces] + sum = 0 + for a in UVAreas: + sum += a + + print("UV Area: ", sum/len(UVAreas)) + return sum/len(UVAreas) + +class UVClass: + def __init__(self, uvs = "selection"): + #Set self.uvs from a list of uvs or automatically + if uvs == "selection": #No list was sent + self.uvs = mc.ls(mc.polyListComponentConversion(tuv=True),fl=True) + else: self.uvs = uvs + + self.type = "standard" + self.shells = [] + self.borderEdges = [] + + def setMinMax(self): + xPositions = sorted([mc.polyEditUV(i, query=True)[0] for i in self.uvs]) + yPositions = sorted([mc.polyEditUV(i, query=True)[1] for i in self.uvs]) + + self.minMax = (xPositions[0],xPositions[-1]),(yPositions[0],yPositions[-1]) + self.xMin = self.minMax[0][0] + self.xMax = self.minMax[0][1] + self.yMin = self.minMax[1][0] + self.yMax = self.minMax[1][1] + + def getPivot(self): + pivot = mc.polyEvaluate(self.uvs,bc2=True) + pivU = ((pivot[0][0] + pivot[0][1]) * 0.5) + pivV = ((pivot[1][0] + pivot[1][1]) * 0.5) + return pivU,pivV + + def getShells(self): + """ This creates a list object (shells) within the class containing a UVClass per shell found""" + if len(self.shells): #No need to do this twice + if self.type == "shell": + print("Class is already of shell type. This function call is redundant") + return + + currentSelection = mc.ls(sl = True) + self.shells = [] + for uv in self.uvs: + found = False + for shell in self.shells: + if uv in shell.uvs: + found = True + if not found: + mc.select(uv) + mel.eval('polySelectBorderShell 0;') + thisShell = UVClass() + thisShell.type = "shell" + thisShell.setMinMax() + + self.shells.append(thisShell) + + mc.select(currentSelection) +#####///////////////////////////////////////##### +##### ##### +##### SHARED PROCEDURES ##### +##### ##### +#####///////////////////////////////////////##### +""" +def selectShellBorderEdges(): + from borders import BorderEdges + selected = UVClass() + selected.getShells() + + edges = BorderEdges(selected.shells) + + mc.select(edges) + return +""" +def selectShellBorderEdges(): + selected = UVClass() + selected.getShells() + finalEdges = [] + + for shell in selected.shells: + + #Get border UVs for first uv in shell + mc.select(shell.uvs[0]) + mel.eval('polySelectBorderShell 1;') + + #Get this shell's BORDER UVS + buvs = mc.ls(sl=True,fl=True) + + #List edges that are connected to uv border + relatedEdges = pm.ls(pm.polyListComponentConversion(te=True),fl=True) + print(relatedEdges) + for edge in relatedEdges: + + # UVClass uses cmds and not pymel, so we need to use it here as well when comparing names + # mc.ls > pCube1.map[0]... + # pm.ls > pCube1Shape1[0] + + #Get CONNECTED UVS + cuvs = mc.ls(pm.polyListComponentConversion(edge, tuv=True),fl=True) + + matches = 0 + for uv in cuvs: + if uv in buvs: matches +=1 + + if matches > 1: + #Converting to string because of weird error: Problem with the API object returned by __apiobject__ method + + matchEdgeFaces = pm.ls(edge.connectedFaces(),fl=True) + if edge.isOnBoundary(): + #Border edge is auto accept + finalEdges.append(str(edge)) + else: + if len(matchEdgeFaces) > 1: + #A triangulated face may have an edge that's not the shell border but both uvs are. + #Here we look to see if both faces connected to that edge are within the uv shell. + #if they are, we discard that edge. + + """ Working on it + facesInShell = 0 + for f in matchEdgeFaces: + fuvs = mc.ls(pm.polyListComponentConversion(matchEdgeFaces, tuv=True),fl=True) + if fuvs[0] in shell.uvs: + print f, "is in shell." + facesInShell +=1 + else: print f, "NOT in shell" + + if facesInShell == 2: + finalEdges.append(str(edge)) + """ + # Partial sollution + mefUVS = mc.ls(pm.polyListComponentConversion(matchEdgeFaces, tuv=True),fl=True) + blab = 0 + for uv in mefUVS: + if uv in buvs: blab += 1 + + if blab < len(mefUVS): + finalEdges.append(str(edge)) + #SELECT! + mc.select(finalEdges) + +def gatherShells(*args): + selected = UVClass() + selected.getShells() + + for shell in selected.shells: + x_center = (shell.xMin + shell.xMax)/2.0 + y_center = (shell.yMin + shell.yMax)/2.0 + if x_center > 1: + mc.polyEditUV(shell.uvs, u= -int(x_center),v=0) + if x_center < 0: + mc.polyEditUV(shell.uvs, u= -int(x_center) + 1,v=0) + if y_center > 1: + mc.polyEditUV(shell.uvs, u= 0, v= -int(y_center)) + if y_center < 0: + mc.polyEditUV(shell.uvs, u= 0, v= -int(y_center) + 1) + return + +def alignShells(dir): + selected = UVClass() + selected.getShells() + + shellsUVs = [] + for shell in selected.shells: + for uv in shell.uvs: + shellsUVs.append(uv) + + allUVs = UVClass(shellsUVs) + allUVs.setMinMax() + + #Move shells + for shell in selected.shells: + if dir == "right": + mc.polyEditUV(shell.uvs, u= allUVs.xMax - shell.xMax) + elif dir == "left": + mc.polyEditUV(shell.uvs, u= allUVs.xMin - shell.xMin) + elif dir == "centerH": + mc.polyEditUV(shell.uvs, u = (allUVs.xMin+allUVs.xMax)/2 - (shell.xMax + shell.xMin)/2) + elif dir == "up": + mc.polyEditUV(shell.uvs, v= allUVs.yMax - shell.yMax) + elif dir == "down": + mc.polyEditUV(shell.uvs, v= allUVs.yMin - shell.yMin) + elif dir == "centerV": + mc.polyEditUV(shell.uvs, v = (allUVs.yMin+allUVs.yMax)/2 - (shell.yMax + shell.yMin)/2) + return + +def align(flag,*args): #Rotate align shell(s) + orgSel = UVClass() + orgSel.getShells() + + alignPoints = [] + if len(orgSel.uvs) < 2: + mel.eval('warning("Select at least two uvs!")') + return + + for shell in orgSel.shells: + #Rotations can be unexpected if more than two uvs per shell were selected + #This is often because orgSel is not in the same order as uvs were selected + uvPositions = {} + for uv in orgSel.uvs: + if uv in shell.uvs and uv not in uvPositions.keys(): + uvPositions[len(uvPositions.items())] = uv + + if len(uvPositions) >= 2: + angle = findAngle(( uvPositions.get(0), uvPositions.get(1) )) + pivot = shell.getPivot() + pu = pivot[0] + pv = pivot[1] + + #Align to hoizontal + if angle >-45 and angle < 45: + mc.polyEditUV (shell.uvs, pu=pu,pv=pv,angle=(0 - angle)) + elif angle >135 and angle < 180: + mc.polyEditUV (shell.uvs, pu=pu,pv=pv,angle=(180 - angle)) + elif angle >-180 and angle <-135: + mc.polyEditUV (shell.uvs, pu=pu,pv=pv,angle=(180 - angle)) + + #Align to vertical + if angle >45 and angle <135: + mc.polyEditUV (shell.uvs, pu=pu,pv=pv,angle=(90 - angle)) + elif angle <-45 and angle >-135: + mc.polyEditUV (shell.uvs, pu=pu,pv=pv,angle=(270 - angle)) + return + +def findAngle(tuple): + #Returns angle of two UV points + uv0 = tuple[0] + uv1 = tuple[1] + p1 = mc.polyEditUV(uv0,q=True) + p2 = mc.polyEditUV(uv1,q=True) + X = (p2[0] - p1[0]) + Y = (p2[1] - p1[1]) + + radians = math.atan2(Y,X) + angle = radians*57.2957795 + + return angle + +def findBox2D(uvs): + xMin = mc.polyEditUV(uvs[0],query=True)[0] + xMax = mc.polyEditUV(uvs[0],query=True)[0] + yMin = mc.polyEditUV(uvs[0],query=True)[1] + yMax = mc.polyEditUV(uvs[0],query=True)[1] + + for u in uvs: + posX = mc.polyEditUV(u,query=True)[0] + posY = mc.polyEditUV(u,query=True)[1] + + if posX > xMax: xMax = posX + elif posX < xMin: xMin = posX + if posY > yMax: yMax = posY + elif posY < yMin: yMin = posY + + return (xMin,xMax), (yMin,yMax) + +def getFileResolution(): + texWinName = mc.getPanel(sty='polyTexturePlacementPanel') + availableImages = mc.textureWindow(texWinName[0], q=True, imn=True) + if availableImages: + currentImage = availableImages[mc.textureWindow(texWinName[0], q=True, imageNumber=True)] + currentImage = currentImage.split()[-1] + + #Find currently used image + if 'outSizeX' in mc.listAttr(currentImage): + x = mc.getAttr(currentImage + '.outSizeX') + y = mc.getAttr(currentImage + '.outSizeY') + return int(x), int(y) + else: return None + +def getWorkingResolution(): + """ + Returns the active "working" resolution, based on whether or not the user + has set UVDeluxe to to manual or automatic texture resolution detection. + """ + checkbox = mc.checkBox('DTR',q=True,v=True) + if checkbox: + resW = float(mc.text('resTextW',q=True,l=True)) + resH = float(mc.text('resTextH',q=True,l=True)) + else: + resW = float(mc.text('resTextManW',q=True,l=True)) + resH = float(mc.text('resTextManH',q=True,l=True)) + + return resW, resH + +def setRatioMultiplier(): + """ + Get the correct multiplier for the pixel ratio calculation. + """ + resolution = getWorkingResolution() + resW = resolution[0] + resH = resolution[1] + + # resH = float(mc.text('resTextH',q=True,l=True)) + if resW > 0: + unit = mc.currentUnit(query=True,f=True) + global mult + mult = 1 + if not unit == 'centimeter': + if unit == 'meter': + mult = 0.01 + elif unit == 'millimeter': + mult = 10 + else: + mc.confirmDialog(title='Sorry!',button='ok',message='UVDeluxe\'s Set Ratio does work with unit type: %s\nPlease work in meters, centimeters or millimeters' % unit); + #mc.error('Script not configured for unit %s' % unit) + + mult = (mult*(8192/resW))/8 + return mult + else: + return 0 + +def straightenEdges(flag): + def findAdjacentEdges(gotEdges): + ## Accepts list of tuples size 2 ## + adjacentEdges = {} + + ##STEP ONE## + #Create list of all UVs in gotEdges + everyUV = [] + for e in range(0,len(gotEdges),1): + for u in range(0,len(gotEdges[e]),1): + if not gotEdges[e][u] in everyUV: + everyUV.append(gotEdges[e][u]) + + #Check which uvs are in more than one edge, and store those edge connections. + pairs = [] + loneEdges = [] + for u in range(0,len(everyUV),1): + #Clear list of found edges for this uv + foundE = [] + for e in range(0,len(gotEdges),1): + if everyUV[u] in gotEdges[e]: + foundE.append(gotEdges[e]) + + #Put edges into appropriate list, right before the loop iteration ends. + if e==len(gotEdges)-1: + if len(foundE) == 2 and not foundE in pairs: pairs.append(foundE) + elif len(foundE) == 1 and not foundE in loneEdges: + loneEdges.append(foundE) + + #Remove pair-ends that got put into loneEdges + for l in range(len(loneEdges)-1,-1,-1): + found = [] + for p in range(0,len(pairs),1): + if loneEdges[l][0] in pairs[p]: + found.append(l) + for f in range(0,len(found),1): + loneEdges.pop(found[f]) + + ## STEP TWO ## + incomplete = True + addNewPair = False + unsortedPairs = pairs[:] + keysComplete = [] + + if len(pairs): + while incomplete: + keysSkipped = 0 + + if len(unsortedPairs): + ### print "\nAdding new key!" + adjacentEdges[len(adjacentEdges)] = unsortedPairs[0] + + for key in adjacentEdges.keys(): + ### print "\nNow checking Key %d" % key + if key not in keysComplete: + #Pop first pair key from unsortet. + popPairs = [] + deletePair = [] + for p in unsortedPairs: + if p == adjacentEdges[key]: + deletePair.append(p) + for p in deletePair: + unsortedPairs.remove(p) + + addingToKey = True + while addingToKey: + uvs_inKey = [] + for pair in adjacentEdges.get(key): + for uv in pair: + uvs_inKey.append(uv) + + popPairs = [] + pairsToAdd = [] + for p in range(0,len(unsortedPairs),1): + for uv in uvs_inKey: + #Check if either edge of pair contains key-uv + if uv in unsortedPairs[p][0] or uv in unsortedPairs[p][1]: + pairsToAdd.append(unsortedPairs[p]) + + #Add pairs to key and remove from list of unsorted pairs + for pair in pairsToAdd: + adjacentEdges[key] += pair + adjacentEdges[key] = sorted(list(set(adjacentEdges[key]))) + if pair in unsortedPairs: + unsortedPairs.remove(pair) + + #Break loop when nothing more to add + if not len(pairsToAdd): + addingToKey = False + keysComplete.append(key) + + else: + #Break loop when all keys are complete + keysSkipped += 1 + if keysSkipped == len(adjacentEdges): + incomplete = False + + #Assign key to every single edge left + for e in loneEdges: + adjacentEdges[len(adjacentEdges)] = e + + #Restructure keys from list of tuples to list of uvs + for key in adjacentEdges.keys(): + uvs_inKey = [] + for tuple in adjacentEdges[key]: + uvs_inKey.append(tuple[0]) + uvs_inKey.append(tuple[1]) + + #Replace list with new list + adjacentEdges[key] = sorted(list(set(uvs_inKey))) + + return adjacentEdges + + max_angle = mc.floatField('angleField', query=True, value=True) + + orgSel = mc.ls(sl = True) + edges = createEdgeList() + + #Sort edges by horizontal and vertical + sortedEdges = sortEdges(edges,max_angle) + + #Determin method of straightening + ## Horizontal + if not flag == 'vert': + hz_edges = findAdjacentEdges(sortedEdges[0]) + + for key in hz_edges.keys(): + box = findBox2D(hz_edges[key]) + centerV=((box[1][0] + box [1][1]) * 0.5) + mc.polyEditUV(hz_edges[key],v=centerV,relative=False) + + ## Vertical + if not flag == 'hori': + vt_edges = findAdjacentEdges(sortedEdges[1]) + + for key in vt_edges.keys(): + box = findBox2D(vt_edges[key]) + centerU=((box[0][0] + box[0][1]) * 0.5) + mc.polyEditUV(vt_edges[key],u=centerU,relative=False) + return + +def createEdgeList(): + def addToEdges(uv0, uv1): + uvTup1 = (uv0,uv1) + uvTup2 = (uv1,uv0) + if not uvTup1 in edges.values() and not uvTup2 in edges.values(): + edges[len(edges)] = uvTup1 + return + + edges = { } + orgSel = mc.ls(sl = True, fl=True) + + ##################### + #Build list of edges + if len(orgSel): + for u in range(0,len(orgSel),1): + uv1 = orgSel[u] + uvToEdge = mc.ls(mc.polyListComponentConversion(orgSel[u], fuv=True, te=True), fl=True) + + #Get uvs in convertToFace selection + faces = mc.ls(mc.polyListComponentConversion(orgSel[u], fuv=True, tf=True), fl=True) + uvGrowSel = [] + for f in faces: + uv = mc.ls(mc.polyListComponentConversion(f, ff=True, tuv=True), fl=True) + for point in uv: + if not point in uvGrowSel: + uvGrowSel.append(point) + + #Check if there is a connection between point 1 and points in grow selection. + for g in uvGrowSel: + e1 = mc.ls(mc.polyListComponentConversion(uv1, fuv=True, te=True), fl=True) + e2 = mc.ls(mc.polyListComponentConversion(g, fuv=True, te=True), fl=True) + se = list(set(e1) & set(e2)) + if len(se): + #There is + ed = mc.ls(mc.polyListComponentConversion(se[0], fe=True, tuv=True), fl=True) + keepUVs = [] + for e in range(len(ed)): + if ed[e] in uvGrowSel and ed[e] in orgSel: + keepUVs.append(ed[e]) + if len(keepUVs) == 2: + addToEdges(keepUVs[0], keepUVs[1]) + + return edges + +def sortEdges(edges, max_angle): + hz_edges = {} + vt_edges = {} + for e in edges: + if not e in hz_edges.values() and not e in vt_edges.values(): + angle = findAngle(edges[e]) + #if not uvTup1 in edges.values() and not uvTup2 in edges.values(): edges[len(edges)] = uvTup1 + if angle < 0: + angle = angle*-1 + #hoizontal + if angle < 45 and angle >= 0: + if angle < max_angle: + hz_edges[len(hz_edges)] = edges[e] + elif angle >= 135 and angle < 180: + if angle > 180-max_angle: + hz_edges[len(hz_edges)] = edges[e] + #vertical + elif angle < 135 and angle >= 45: + if angle < 90 and angle > 90-max_angle: + vt_edges[len(vt_edges)] = edges[e] + elif angle > 90 and angle < 90+max_angle: + vt_edges[len(vt_edges)] = edges[e] + return hz_edges,vt_edges diff --git a/Scripts/Modeling/UV/UVSetEditor.py b/Scripts/Modeling/UV/UVSetEditor.py new file mode 100644 index 0000000..4b9edc9 --- /dev/null +++ b/Scripts/Modeling/UV/UVSetEditor.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import maya.cmds as cmds + +# Define minimum window width and height +MIN_WINDOW_WIDTH = 300 # Increased from 200 to 300 +MIN_WINDOW_HEIGHT = 300 + +# Function: Get and display UV sets +def refresh_uv_sets(): + selection = cmds.ls(selection=True) + if not selection: + cmds.warning("Please select an object first.") + return + + selected_object = selection[0] + uv_sets = cmds.polyUVSet(selected_object, query=True, allUVSets=True) + cmds.textScrollList('uvList', edit=True, removeAll=True) + for uv_set in uv_sets: + cmds.textScrollList('uvList', edit=True, append=uv_set) + +# Function: Switch UV set +def switch_uv_set(*args): + selected_uv_set = cmds.textScrollList('uvList', query=True, selectItem=True) + if selected_uv_set: + selected_object = cmds.ls(selection=True) + if selected_object: + selected_object = selected_object[0] + # Switch current UV set + cmds.polyUVSet(selected_object, currentUVSet=True, uvSet=selected_uv_set[0]) + print(f"Switched to UV set: {selected_uv_set[0]}") + else: + cmds.warning("Please select an object.") + else: + cmds.warning("Please select a UV set.") + +# Function: Delete selected UV set +def delete_selected_uv_set(): + selected_uv_set = cmds.textScrollList('uvList', query=True, selectItem=True) + if selected_uv_set: + cmds.polyUVSet(delete=True, uvSet=selected_uv_set[0]) + refresh_uv_sets() + +# Function: Rename selected UV set +def rename_selected_uv_set(new_name): + selected_uv_set = cmds.textScrollList('uvList', query=True, selectItem=True) + if selected_uv_set: + if new_name: + cmds.polyUVSet(rename=True, newUVSet=new_name, uvSet=selected_uv_set[0]) + refresh_uv_sets() + cmds.textFieldGrp('newNameField', edit=True, text='') # Clear input field content + else: + cmds.warning("Please enter a new name.") + else: + cmds.warning("Please select a UV set first.") + +# Function: Create new UV set +def create_new_uv_set(new_name): + if new_name: + cmds.polyUVSet(create=True, uvSet=new_name) + refresh_uv_sets() + cmds.textFieldGrp('newNameField', edit=True, text='') # Clear input field content + else: + cmds.warning("Please enter a name for the new UV set.") + +# Function: Set UV set 1 name +def set_uv_set1_name(*args): + selected_uv_set = cmds.textScrollList('uvList', query=True, selectItem=True) + if selected_uv_set: + cmds.textFieldGrp("uvSet1", edit=True, text=selected_uv_set[0]) + else: + cmds.warning("Please select a UV set first.") + +# Function: Set UV set 2 name +def set_uv_set2_name(*args): + selected_uv_set = cmds.textScrollList('uvList', query=True, selectItem=True) + if selected_uv_set: + cmds.textFieldGrp("uvSet2", edit=True, text=selected_uv_set[0]) + else: + cmds.warning("Please select a UV set first.") + +# Function: UV set swap +def UVsetSwap(*args): + UVname1 = cmds.textFieldGrp("uvSet1", query=True, text=True) + UVname2 = cmds.textFieldGrp("uvSet2", query=True, text=True) + cmds.polyUVSet(query=True, allUVSets=True) + cmds.polyUVSet(create=True, uvSet='TempUV') + cmds.polyUVSet(copy=True, nuv='TempUV', uvSet=UVname1) + cmds.polyUVSet(copy=True, nuv=UVname1, uvSet=UVname2) + cmds.polyUVSet(copy=True, nuv=UVname2, uvSet='TempUV') + cmds.polyUVSet(delete=True, uvSet='TempUV') + refresh_uv_sets() # Refresh list after execution + +def UVsetReorder(*args): + UVname1 = cmds.textFieldGrp("uvSet1", query=True, text=True) + UVname2 = cmds.textFieldGrp("uvSet2", query=True, text=True) + print("Reorder object is " + UVname1 + " + " + UVname2) + cmds.polyUVSet(reorder=True, uvSet=UVname1, newUVSet=UVname2) + UVobj = cmds.ls(sl=True) + cmds.select(UVobj) + refresh_uv_sets() # Refresh list after execution + +# Function: UV set transfer +def get_object_name(*args): + # Get currently selected object and fill its name in the text field + selected = cmds.ls(sl=True) + if selected: + cmds.textField('objectNameField', edit=True, text=selected[0]) + else: + cmds.warning("No object selected.") + +def set_uv(*args): + # Get source and target objects, perform UV transfer, and clean up history + source_object = cmds.textField('objectNameField', query=True, text=True) + target_object = cmds.ls(sl=True) + if not source_object or not target_object: + cmds.warning("Please ensure both source and target objects are selected.") + return + target_object = target_object[0] + sample_space_dict = {'World': 0, 'Local': 1, 'UV': 5, 'Component': 4} + sample_space = cmds.radioCollection('sampleSpaceRadio', query=True, select=True) + sample_space = cmds.radioButton(sample_space, query=True, label=True) + sample_space = sample_space_dict.get(sample_space, 0) + cmds.transferAttributes(source_object, target_object, transferPositions=0, transferNormals=0, transferUVs=2, transferColors=0, sampleSpace=sample_space, searchMethod=3) + cmds.delete(target_object, constructionHistory=True) # Clean up history + +def on_window_resize(*args): + window_name = "UVSetEditor" + # Get current window size + current_width = cmds.window(window_name, query=True, width=True) + current_height = cmds.window(window_name, query=True, height=True) + + # Check and limit window size + if current_width < MIN_WINDOW_WIDTH: + cmds.window(window_name, edit=True, width=MIN_WINDOW_WIDTH) + if current_height < MIN_WINDOW_HEIGHT: + cmds.window(window_name, edit=True, height=MIN_WINDOW_HEIGHT) + +def show(*args): + window_name = "UVSetEditor" + # Check if window exists, if so, delete it + if cmds.window(window_name, exists=True): + cmds.deleteUI('UV Set Editor', window=True) + # Window + # Create a new window and set its title and initial size + window = cmds.window(window_name, title=" UV Set Editor", widthHeight=(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT), sizeable=True, tlb=True) # tlb=True + + cmds.frameLayout(label='UV-Set') + cmds.columnLayout(adjustableColumn=True) + # Create a textScrollList control and set the selection change command + cmds.textScrollList('uvList', numberOfRows=8, allowMultiSelection=False, width=280, selectCommand=switch_uv_set) + cmds.textFieldGrp('newNameField', placeholderText=' Enter new name, then click Re to rename', width=280, columnAlign=[1, 'center'] ,columnWidth=[1,280]) + cmds.rowLayout(numberOfColumns=4, + columnWidth4=(65, 65, 65, 65), + columnAttach4=('both', 'both', 'both', 'both')) + cmds.button( label='Get', height=32, command=lambda x: refresh_uv_sets(),backgroundColor=(0.53, 0.81, 0.98)) + cmds.button( label='Del', height=32, command=lambda x: delete_selected_uv_set()) + cmds.button( label='New', height=32, command=lambda x: create_new_uv_set(cmds.textFieldGrp('newNameField', query=True, text=True))) + cmds.button( label='Re', height=32, command=lambda x: rename_selected_uv_set(cmds.textFieldGrp('newNameField', query=True, text=True))) + cmds.setParent( '..' ) + + cmds.setParent('..') # End current form layout + + cmds.frameLayout(label='UV-Swap') + cmds.columnLayout(adjustableColumn=True, width=280) + cmds.text(l='Enter UV set names in "uv1" and "uv2"', h=15) + cmds.text(l=' UV swap or reorder swap. ', h=15) + cmds.text(l='', h=5) + cmds.rowLayout(numberOfColumns=3, columnWidth3=(65, 65, 130), columnAttach3=('both', 'both', 'both')) + cmds.button(label='Get', height=25, command=set_uv_set1_name, backgroundColor=(0.53, 0.81, 0.98)) + cmds.textFieldGrp("uvSet1", placeholderText='uv1', editable=True, width=200) + cmds.setParent('..') + cmds.rowLayout(numberOfColumns=3, columnWidth3=(65, 65, 130), columnAttach3=('both', 'both', 'both')) + cmds.button(label='Get', height=25, command=set_uv_set2_name, backgroundColor=(0.53, 0.81, 0.98)) + cmds.textFieldGrp("uvSet2", placeholderText='uv2', editable=True, width=200) + cmds.setParent('..') + cmds.rowColumnLayout(numberOfColumns=2, columnWidth=[(1, 135), (2, 135)]) + cmds.button(label='UV Swap', command=UVsetSwap, backgroundColor=(0.53, 0.81, 0.98)) + cmds.button(label='Reorder Swap', command=UVsetReorder, backgroundColor=(0.53, 0.81, 0.98)) + + UVname1 = cmds.textFieldGrp("uvSet1", query=True, text=True) + UVname2 = cmds.textFieldGrp("uvSet2", query=True, text=True) + print("Now we have UVset = {}, {}".format(UVname1, UVname2)) + + cmds.setParent('..') # End current form layout + + # Separator + cmds.separator(height=20, style='in') + + # Create a column layout, all child elements will be vertically arranged + cmds.frameLayout(label='UV-Transfer') + cmds.columnLayout(adjustableColumn=True, width=230, height=130) + cmds.rowLayout(numberOfColumns=3, columnWidth3=(50, 100, 50)) + cmds.button(label='Get', command=get_object_name, backgroundColor=(0.53, 0.81, 0.98), width=45) # Create a button that calls get_object_name function when clicked + cmds.textField('objectNameField', enable=False, width=120) # Create a text field to display the name of the selected object + cmds.button(label='Set', command=set_uv, backgroundColor=(0.53, 0.81, 0.98), width=45) # Create a button that calls set_uv function when clicked + cmds.setParent('..') # End current form layout + + # cmds.frameLayout(label='Sample Space') + cmds.text(l='Sample Space:', h=20, align='left') + form = cmds.formLayout() + + cmds.radioCollection('sampleSpaceRadio') # Create a radio button group + rb1 = cmds.radioButton(label='World', select=True) # Create a radio button + rb2 = cmds.radioButton(label='Local') + rb3 = cmds.radioButton(label='UV') + rb4 = cmds.radioButton(label='Component') + # Set form layout parameters to keep radio buttons horizontally aligned and centered when window size changes + cmds.formLayout(form, edit=True, attachForm=[(rb1, 'left', 10), (rb4, 'right', 10)], attachControl=[(rb2, 'left', 5, rb1), (rb3, 'left', 5, rb2), (rb4, 'left', 5, rb3)]) + cmds.setParent('..') # End current form layout + + cmds.scriptJob(event=["idle", on_window_resize], parent=window) # Listen for window resize events + + cmds.showWindow(window) # Show window \ No newline at end of file