diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlusBase.py b/Scripts/Modeling/Edit/CreasePlus/CreasePlusBase.py new file mode 100644 index 0000000..c667b7a --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/CreasePlusBase.py @@ -0,0 +1,479 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +_____ _____ ______ _____ ______ +/ ____| __ \| ____| /\ / ____| ____|_ +| | | |__) | |__ / \ | (___ | |__ _| |_ +| | | _ /| __| / /\ \ \___ \| __|_ _| +| |____| | \ \| |____ / ____ \ ____) | |____|_| +\_____|_| \_\______/_/ \_\_____/|______| + +""" + +import maya.cmds as cmds +import maya.api.OpenMaya as om + +# uses python maya 2 + +maya_useNewAPI = True + + +class MayaVerObj(): + def __init__(self): + self.num = None + self.extnum = 0 + + +def cPwhatmayaversion(): + verstring = cmds.about(v=True) + + splited = verstring.split() + num = None + extnum = 0 + + for r in splited: + if "20" in r: + num = int(r) + break + + i = -1 + for r in splited: + i += 1 + if r.lower() == "extension" or r.lower() == "ext": + j = i+1 + for j in range(len(splited)): + if splited[j].isdigit(): + extnum = int(splited[j]) + break + break + + if not num: + raise Exception("can't get maya version") + + mayavobj = MayaVerObj() + mayavobj.num = num + mayavobj.extnum = extnum + return mayavobj + +global_creasePlus_mayaver = cPwhatmayaversion() + +def getmayaver(): + global global_creasePlus_mayaver + return global_creasePlus_mayaver + + +class CpMsg: + + kNoSel = 'nothing is selected' + kNoSelCurve = 'no curve(s) selected' + kNoSelMesh = 'no mesh(es) selected' + kNoSelEdge = 'no edge(s) selected' + kNoSelVert = 'no vertice(s) selected' + kNoSelFace = 'no face(s) selected' + + kSelOneMesh = 'select one mesh' + kSelMesh = 'mesh(es) must be selected' + kSelEdge = 'edge(s) must be selected' + kSelFace = 'face(s) must be selected' + kSelVert = 'vertice(s) must be selected' + + kSelEdgeOrOneMesh = 'select edge(s) or a mesh' + + kSelCurveCv = 'curve cv(s) must be selected' + kWorksForOne = 'works just for one entity' + + kNoHardEdges = 'no hard edge(s) were found' + + kSelLeastTwoMesh = 'select at least two meshes' + kSelLeastTwoCurve = 'select at least two curves' + + kInvalidFuncArgs = 'function called with invalid arguments' + + kRequModelView = 'you are required to be in a modeling view/pan' + kWrongSel = 'wrong selection' + + kcPnodePluginNotLoaded = \ + 'creasePlus nodes plugin must be loaded to use this command' + + +# obj assumed to be a parent +def cPgotoChild(obj, mfntyp): + dagn = om.MFnDagNode(obj) + for i in range(dagn.childCount()): + cur = dagn.child(i) + if cur.hasFn(mfntyp): + return cur + return om.MObject.kNullObj + + +def cPshapeDagPath(obj): + dagn = om.MFnDagNode(obj) + return dagn.getPath() + + +def cPhardEdgesStrings(shape): + + sel = om.MSelectionList() + sel.add(shape) + + dagp = sel.getDagPath(0) + + edgeIter = om.MItMeshEdge(dagp) + selStrings = [] + shapeString = dagp.partialPathName() + while not edgeIter.isDone(): + if edgeIter.isSmooth == False: + selStrings.append(shapeString + '.e[' + + str(edgeIter.index()) + ']') + + edgeIter.next() + + return selStrings + + +def cPgetShapeStringsFromSel(mfntyp): + + sel = om.MGlobal.getActiveSelectionList() + selIt = om.MItSelectionList(sel) + + selStrings = [] + dagFn = om.MFnDagNode() + while not selIt.isDone(): + + if selIt.itemType() != selIt.kDagSelectionItem: + selIt.next() + continue + + obj = selIt.getDependNode() + + if not obj.hasFn(mfntyp): + + obj = cPgotoChild(obj, mfntyp) + + if not obj.hasFn(mfntyp): + selIt.next() + continue + + dagFn.setObject(obj) + selStrings.append(dagFn.partialPathName()) + selIt.next() + + return selStrings + + +def cPcameraDominantPlane(): + + activePan = cmds.getPanel(wf=True) + + if cmds.getPanel(to=activePan) != 'modelPanel': + cmds.error(CpMsg.kRequModelView) + + camPos = cmds.camera(cmds.modelEditor(activePan, q=True, cam=True), + q=True, p=True) + camTarget = cmds.camera(cmds.modelEditor(activePan, q=True, cam=True), + q=True, wci=True) + + camDir = om.MVector(abs(camTarget[0] - camPos[0]), abs(camTarget[1] + - camPos[1]), abs(camTarget[2] - camPos[2])) + + maxv = 0 + ddir = 'x' + if maxv < camDir.x: + maxv = camDir.x + ddir = 'x' + if maxv < camDir.y: + maxv = camDir.y + ddir = 'y' + if maxv < camDir.z: + maxv = camDir.z + ddir = 'z' + + return ddir + + +# cmds.ls(sl=True) +# get shape + v, e, f comps in a tuple + +def cPgetShapeAndCoStrings(mselit): + + shape = None + v = None + e = None + f = None + + if mselit.itemType() != mselit.kDagSelectionItem: + return (shape, v, e, f) + + hasComp = mselit.hasComponents() + + if hasComp == True: + + comp = mselit.getComponent() + + if comp[0].node().hasFn(om.MFn.kMesh): + shape = comp[0] + + if comp[1].hasFn(om.MFn.kMeshPolygonComponent): + f = comp[1] + elif comp[1].hasFn(om.MFn.kMeshEdgeComponent): + + e = comp[1] + elif comp[1].hasFn(om.MFn.kMeshVertComponent): + + v = comp[1] + else: + + obj = mselit.getDependNode() + + if not obj.hasFn(om.MFn.kMesh): + obj = cPgotoChild(obj, om.MFn.kMesh) + + if obj.hasFn(om.MFn.kMesh): + shape = cPshapeDagPath(obj) + + return (shape, v, e, f) + + +def cPfaceToHardEdgeStrings(dagp, faceComps): + + edgeIds = set() + + faceIt = om.MItMeshPolygon(dagp, faceComps) + meshFn = om.MFnMesh(dagp.node()) + + while not faceIt.isDone(): + + fEdges = faceIt.getEdges() + + for idx in fEdges: + if not meshFn.isEdgeSmooth(idx): + edgeIds.add(idx) + + faceIt.next(None) # maya api bug + + shapeString = dagp.partialPathName() + return [shapeString + '.e[' + str(idx) + ']' for idx in edgeIds] + + +def cPedgeToStrings(dagp, edgeComps): + + edgeStrings = [] + edgeIt = om.MItMeshEdge(dagp, edgeComps) + shapeString = dagp.partialPathName() + while not edgeIt.isDone(): + + edgeStrings.append(shapeString + '.e[' + str(edgeIt.index()) + + ']') + edgeIt.next() + + return edgeStrings + + +################################################################ CONTEXTS ################################################################# + +# draw curve ctx +try: + global_cPcurveCtxStr +except: + global_cPcurveCtxStr = cmds.curveCVCtx('cPcurveCtx', degree=1) + +# + +# booleanop context +try: + global_cPboolOpCtxStr +except: + global_cPboolOpCtxStr = cmds.dragAttrContext('cPboolOpCtx') + +global_cPboolOpAttrCnt = 3 +global_cPboolOpAttrIter = 0 + + +def cPboolOpIterSetVal(val): + global global_cPboolOpAttrIter + global_cPboolOpAttrIter = val + return global_cPboolOpAttrIter % global_cPboolOpAttrCnt + + +def cPboolOpIterVal(): + global global_cPboolOpAttrIter + return global_cPboolOpAttrIter % global_cPboolOpAttrCnt + + +def cPboolOpIterIncVal(): + global global_cPboolOpAttrIter + global_cPboolOpAttrIter += 1 + return global_cPboolOpAttrIter % global_cPboolOpAttrCnt + + +# + +# mirror context +try: + global_cPmirrorCtxStr +except: + global_cPmirrorCtxStr = cmds.dragAttrContext('cPmirrorCtx') + +global_cPmirrorAttrCnt = None +if getmayaver().num > 2016: + global_cPmirrorAttrCnt = 1 +else: + global_cPmirrorAttrCnt = 3 + +global_cPmirrorAttrIter = 0 + + +def cPmirrorIterSetVal(val): + global global_cPmirrorAttrIter + global_cPmirrorAttrIter = val + return global_cPmirrorAttrIter % global_cPmirrorAttrCnt + + +def cPmirrorIterVal(): + global global_cPmirrorAttrIter + return global_cPmirrorAttrIter % global_cPmirrorAttrCnt + + +def cPmirrorIterIncVal(): + global global_cPmirrorAttrIter + global_cPmirrorAttrIter += 1 + return global_cPmirrorAttrIter % global_cPmirrorAttrCnt + + +# + +# hbevel context +try: + global_hBevelCtxStr +except: + global_hBevelCtxStr = cmds.dragAttrContext('hBevelCtx') + +global_hBevelAttrCnt = 2 +global_hBevelAttrIter = 0 + + +def cPhBevelIterSetVal(val): + global global_hBevelAttrIter + global_hBevelAttrIter = val + return global_hBevelAttrIter % global_hBevelAttrCnt + + +def cPhBevelIterVal(): + global global_hBevelAttrIter + return global_hBevelAttrIter % global_hBevelAttrCnt + + +def cPhBevelIterIncVal(): + global global_hBevelAttrIter + global_hBevelAttrIter += 1 + return global_hBevelAttrIter % global_hBevelAttrCnt + + +# + +# curvebevel context +try: + global_cPcurveBevelCtxStr +except: + global_cPcurveBevelCtxStr = cmds.dragAttrContext('cPcurveBvlCtx') + +global_cPcurveBevelAttrCnt = 2 +global_cPcurveBevelAttrIter = 0 + + +def cPcurveBevelIterSetVal(val): + global global_cPcurveBevelAttrIter + global_cPcurveBevelAttrIter = val + return global_cPcurveBevelAttrIter % global_cPcurveBevelAttrCnt + + +def cPcurveBevelIterVal(): + global global_cPcurveBevelAttrIter + return global_cPcurveBevelAttrIter % global_cPcurveBevelAttrCnt + + +def cPcurveBevelIterIncVal(): + global global_cPcurveBevelAttrIter + global_cPcurveBevelAttrIter += 1 + return global_cPcurveBevelAttrIter % global_cPcurveBevelAttrCnt + + +# + +# crease tool context +try: + global_cPcreaseCtxStr +except: + global_cPcreaseCtxStr = cmds.polyCreaseCtx('cPcreaseCtx', es=True, r=True) + +# + +# physical crease context +try: + global_pCreaseCtxStr +except: + global_pCreaseCtxStr = cmds.dragAttrContext('pCreaseCtx') + +global_pCreaseAttrCnt = 2 +global_pCreaseAttrIter = 0 + + +def cPpCreaseIterSetVal(val): + global global_pCreaseAttrIter + global_pCreaseAttrIter = val + return global_pCreaseAttrIter % global_pCreaseAttrCnt + + +def cPpCreaseIterVal(): + global global_pCreaseAttrIter + return global_pCreaseAttrIter % global_pCreaseAttrCnt + + +def cPpCreaseIterIncVal(): + global global_pCreaseAttrIter + global_pCreaseAttrIter += 1 + return global_pCreaseAttrIter % global_pCreaseAttrCnt + + +# +def cPcontextUndo(): + if cmds.currentCtx() == global_cPboolOpCtxStr: + # cmds.setToolTo('moveSuperContext') + cmds.dragAttrContext(global_cPboolOpCtxStr, e=True, reset=True) + cmds.setToolTo(global_cPboolOpCtxStr) + elif cmds.currentCtx() == global_hBevelCtxStr: + + # cmds.setToolTo('moveSuperContext') + cmds.dragAttrContext(global_hBevelCtxStr, e=True, reset=True) + cmds.setToolTo(global_hBevelCtxStr) + elif cmds.currentCtx() == global_cPmirrorCtxStr: + + # cmds.setToolTo('moveSuperContext') + cmds.dragAttrContext(global_cPmirrorCtxStr, e=True, reset=True) + cmds.setToolTo(global_cPmirrorCtxStr) + elif cmds.currentCtx() == global_cPcurveBevelCtxStr: + + # cmds.setToolTo('moveSuperContext') + cmds.dragAttrContext(global_cPcurveBevelCtxStr, e=True, reset=True) + cmds.setToolTo(global_cPcurveBevelCtxStr) + elif cmds.currentCtx() == global_pCreaseCtxStr: + + # cmds.setToolTo('moveSuperContext') + cmds.dragAttrContext(global_pCreaseCtxStr, e=True, reset=True) + cmds.setToolTo(global_pCreaseCtxStr) + + +try: + global_creasePlusCtxUndoJob +except: + global_creasePlusCtxUndoJob = cmds.scriptJob(event=['Undo', cPcontextUndo]) + +############################################################################################################ + +def main(): + return None + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlusCore.py b/Scripts/Modeling/Edit/CreasePlus/CreasePlusCore.py new file mode 100644 index 0000000..a03262f --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/CreasePlusCore.py @@ -0,0 +1,2125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# import sys +""" +_____ _____ ______ _____ ______ +/ ____| __ \| ____| /\ / ____| ____|_ +| | | |__) | |__ / \ | (___ | |__ _| |_ +| | | _ /| __| / /\ \ \___ \| __|_ _| +| |____| | \ \| |____ / ____ \ ____) | |____|_| +\_____|_| \_\______/_/ \_\_____/|______| + +""" + +import math + +from MayaUndoRun import mayaUndoRun +import maya.cmds as mc +import maya.mel as mel +import maya.api.OpenMaya as om + +import CreasePlusExcept as crepexcept +import CreasePlusBase as crep +import CreasePlusNodes +import importlib + +# TODO remove reloads +# crep = importlib.reload(crep) +# CreasePlusNodes = importlib.reload(CreasePlusNodes) + +# uses python maya 2 + +maya_useNewAPI = True + +def cPmayaScriptDir(): + return mc.internalVar(usd=True) + +def cPmainDir(): + return cPmayaScriptDir() + 'CreasePlus/' + +# GLOBALS + +__ = str(CreasePlusNodes) +__ = __[__.find(" \'") + 2:__.find("\' from")] +__ = __[__.rfind('.') + 1:] + +global_CP_Nodes_pluginstr = __ + + +# bool namespace +try: + global_cPboolNamespace +except: + global_cPboolNamespace = mc.namespace(add='cPbool', parent=':') + + +def cPaddToMayaNamespace(toRename, newname): + global global_cPboolNamespace + if not mc.namespace(exists=global_cPboolNamespace): + global_cPboolNamespace = mc.namespace(add='cPbool', parent=':') + + return mc.rename(toRename, newname) + + +##### + + +def creasePlusShapeShifter(): + + sdir = mc.internalVar( + usd=True) + 'AMTools/AMTScripts/StartShapeShifter.mel' + ssExist = mel.eval('filetest -f "' + sdir + '";') + + # welcome + + if not ssExist: + mc.warning( + 'unable to start ShapeShifter, you have to purchase or update ShapeShifter for CREASE+ support.\n' + ) + return + + mel.eval('source "' + sdir + '";') + + +def cPgetGoz(): + if mc.about(win=True): + # for windows + assumed = "C:/Users/Public/Pixologic/GoZApps/Maya/GoZBrushFromMaya.mel" + gozTest = mel.eval("filetest -f " + "\"" + assumed + "\";") + sGoz = "source " + "\"" + assumed + "\";" + if gozTest == 0: + mc.warning("To use this feature you need Goz script from Pixologic Zbrush.\n") + return + else: + mel.eval(sGoz) + else: + + # for mac + assumed = "/Users/Shared/Pixologic/GoZApps/Maya/GoZBrushFromMaya.mel" + gozTest = mel.eval("filetest -f " + "\"" + assumed + "\";") + sGoz = "source " + "\"" + assumed + "\";" + if gozTest == 0: + mc.warning("To use this feature you need Goz script from Pixologic Zbrush.\n") + return + else: + mel.eval(sGoz) + + + + +def creasePlusGoz(): + + osel = mc.ls(sl=True) + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelMesh) + + for shapeStr in sel: + mc.select(shapeStr, r=True) + mel.eval( + 'polyCleanupArgList 4 { "0","2","0","0","1","0","0","0","0","1e-005","0","1e-005","0","1e-005","0","-1","0","0" };' + ) + if len(mc.ls(sl=True, fl=True)): + mel.eval( + 'polyCleanupArgList 4 { "0","1","0","0","1","0","0","0","0","1e-005","0","1e-005","0","1e-005","0","-1","0","0" };' + ) + + mc.select(osel, r=True) + cPgetGoz() + + +@mayaUndoRun +def creasePlusSmooth30(): + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSel) + + for shapeStr in sel: + mc.polySoftEdge(shapeStr, a=30, ch=True) + mc.select( + mc.listRelatives(sel, parent=True, fullPath=True, typ='transform'), + r=True) + + +# creasePlusDisplayHardEdges(0) + + +def creasePlusDisplayHardEdges(disp=0): + + # disp = 0 for toggle + # = 1 to display hard edges + # = 2 for no display + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + for shapeStr in sel: + + if disp == 0: + nodisp = mc.polyOptions(shapeStr, q=True, ae=True)[0] + if nodisp: + mc.polyOptions(shapeStr, hec=True) + else: + mc.polyOptions(shapeStr, ae=True) + elif disp == 1: + mc.polyOptions(shapeStr, hec=True) + elif disp == 2: + + mc.polyOptions(shapeStr, ae=True) + else: + raise crepexcept.cPexcept('wrong kwarg value') + + +@mayaUndoRun +def creasePlusSelHardEdges(): + sel = om.MGlobal.getActiveSelectionList() + selIt = om.MItSelectionList(sel) + + finalSel = om.MSelectionList() + + while not selIt.isDone(): + + obj = selIt.getDependNode() + + if not obj.hasFn(om.MFn.kMesh): + + obj = crep.cPgotoChild(obj, om.MFn.kMesh) + + if not obj.hasFn(om.MFn.kMesh): + selIt.next() + continue + + dagp = crep.cPshapeDagPath(obj) + edgeIter = om.MItMeshEdge(dagp) + + while not edgeIter.isDone(): + + if not edgeIter.isSmooth: + + finalSel.add( + (dagp, edgeIter.currentItem()), mergeWithExisting=False) + + edgeIter.next() + + selIt.next() + + om.MGlobal.setActiveSelectionList(finalSel) + +# @mayaUndoRun +def creasePlusToggleEdgeSmooth(): + + sel = om.MGlobal.getActiveSelectionList() + selIt = om.MItSelectionList(sel) + selIt.setFilter(om.MFn.kMeshEdgeComponent) + + comps = [] + while not selIt.isDone(): + comps.append(selIt.getComponent()) + selIt.next() + + for comp in comps: + toHardList = [] + toSoftenList = [] + + edgeIter = om.MItMeshEdge(comp[0], comp[1]) + + while not edgeIter.isDone(): + + compstring = comp[0].partialPathName() + '.e[' + str( + edgeIter.index()) + ']' + + if edgeIter.isSmooth: + toHardList.append(compstring) + else: + toSoftenList.append(compstring) + + edgeIter.next() + + if len(toHardList): + mc.polySoftEdge(toHardList, a=0, ch=True) + + if len(toSoftenList): + mc.polySoftEdge(toSoftenList, a=180, ch=True) + + om.MGlobal.setActiveSelectionList(sel) + + +def cPdoPolyBevel(selStrings, nname): + if crep.getmayaver().num > 2016 or (crep.getmayaver().num == 2016 and crep.getmayaver().extnum == 2): + nodestr = mc.polyBevel3( + selStrings, + oaf=False, + af=True, + mia=0, + c=True, + f=0, + o=0, + sg=1, + ws=True, + sa=180, + sn=True, + ma=180, + at=180, + )[0] + else: + nodestr = mc.polyBevel3( + selStrings, + oaf=False, + af=True, + fn=1, + o=0, + sg=1, + ws=True, + sa=180, + )[0] + + + + return mc.rename(nodestr, nname) + + +# + +class CpMirrorStat: + + axisStr = 'axis' + sideStr = 'side' + posStr = 'position' + dxs = 'dX' + dys = 'dY' + dzs = 'dZ' + + +def cPmirrorAttrs(nodeString, mirrorNodeStr): + axisStr = CpMirrorStat.axisStr + sideStr = CpMirrorStat.sideStr + posStr = CpMirrorStat.posStr + dxs = CpMirrorStat.dxs + dys = CpMirrorStat.dys + dzs = CpMirrorStat.dzs + + if crep.getmayaver().num > 2016: + + if mc.attributeQuery(axisStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + axisStr) + if mc.attributeQuery(sideStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + sideStr) + if mc.attributeQuery(posStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + posStr) + + mc.addAttr( + nodeString, + ln=axisStr, + k=True, + at='enum', + en='X=0:Y=1:Z=2', + dv=0, + ) + mc.addAttr( + nodeString, + ln=sideStr, + k=True, + at='enum', + en='+=0:-=1', + dv=0, + ) + mc.addAttr( + nodeString, + ln=posStr, + k=True, + at='floatLinear', + dv=mc.getAttr(mirrorNodeStr + '.mirrorPosition')) + + mc.connectAttr(nodeString + '.' + axisStr, mirrorNodeStr + '.axis') + mc.connectAttr(nodeString + '.' + sideStr, + mirrorNodeStr + '.axisDirection') + mc.connectAttr(nodeString + '.' + posStr, + mirrorNodeStr + '.mirrorPosition') + + else: + + if mc.attributeQuery(axisStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + axisStr) + if mc.attributeQuery(dxs , node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + dxs ) + if mc.attributeQuery(dys , node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + dys ) + if mc.attributeQuery(dzs , node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + dzs ) + + mc.addAttr( + nodeString, + ln=axisStr, + k=True, + at='enum', + en='+X=0:-X=1:+Y=2:-Y=3:+Z=4:-Z=5', + dv=0, + ) + + mc.addAttr( + nodeString, + ln=dxs, + k=True, + at='doubleLinear', + dv=0.5) + # dv=mc.getAttr(mirrorNodeStr + '.pivotX')) + mc.addAttr( + nodeString, + ln=dys, + k=True, + at='doubleLinear', + dv=0.5) + # dv=mc.getAttr(mirrorNodeStr + '.pivotY')) + mc.addAttr( + nodeString, + ln=dzs, + k=True, + at='doubleLinear', + dv=0.5) + # dv=mc.getAttr(mirrorNodeStr + '.pivotZ')) + + mc.connectAttr(nodeString + '.' + axisStr, mirrorNodeStr + '.direction') + mc.connectAttr(nodeString + "." + dxs, mirrorNodeStr + '.pivotX') + mc.connectAttr(nodeString + "." + dys, mirrorNodeStr + '.pivotY') + mc.connectAttr(nodeString + "." + dzs, mirrorNodeStr + '.pivotZ') + + +def creasePlusMirrorCont(nodeString): + + crep.global_cPmirrorCtxStr + + axisStr = CpMirrorStat.axisStr + sideStr = CpMirrorStat.sideStr + posStr = CpMirrorStat.posStr + dxs = CpMirrorStat.dxs + dys = CpMirrorStat.dys + dzs = CpMirrorStat.dzs + + cur = None + if mc.currentCtx() == crep.global_cPmirrorCtxStr: + cur = crep.cPmirrorIterIncVal() + else: + cur = crep.cPmirrorIterSetVal(0) + + mc.dragAttrContext(crep.global_cPmirrorCtxStr, e=True, reset=True) + + if crep.getmayaver().num > 2016: + + if cur == 0: + mc.dragAttrContext( + crep.global_cPmirrorCtxStr, e=True, ct=nodeString + '.' + posStr) + else: + if cur == 0: + mc.setAttr(nodeString + "." + axisStr, 0) + elif cur == 1: + mc.setAttr(nodeString + "." + axisStr, 2) + elif cur == 2: + mc.setAttr(nodeString + "." + axisStr, 4) + + + mc.setToolTo(crep.global_cPmirrorCtxStr) + + +def cPhasMirrorHistory(shape): + + dummy = om.MSelectionList() + dummy.add(shape) + dagFn = om.MFnDagNode(dummy.getDependNode(0)) + dagFn.setObject(dagFn.parent(0)) + transfrmStr = dagFn.partialPathName() + + if mc.attributeQuery(CpMirrorStat.axisStr, node=transfrmStr, ex=True): + destPlugs = mc.connectionInfo( + transfrmStr + '.' + CpMirrorStat.axisStr, dfs=True) + for plugStr in destPlugs: + if mc.nodeType(cPplugNode(plugStr)) == 'polyMirror': + return True + else: + + return False + + return False + + +@mayaUndoRun +def creasePlusMirror(): + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelMesh) + + crep.global_cPmirrorCtxStr + ctxActive = mc.currentCtx() == crep.global_cPmirrorCtxStr + + if ctxActive and cPhasMirrorHistory(sel[0]): + transformStr = mc.listRelatives( + sel[0], parent=True, fullPath=True, typ='transform')[0] + creasePlusMirrorCont(transformStr) + return + + transformNodes = [] + for shapeStr in sel: + + # shapeStr = "pCubeShape1" + + transformStr = mc.listRelatives( + shapeStr, parent=True, fullPath=True, typ='transform')[0] + transformNodes.append(transformStr) + mirNode = mc.polyMirrorFace(shapeStr)[0] + # mc.polyMergeVertex(shapeStr, d=0.015, am=False) + cPmirrorAttrs(transformStr, mirNode) + + mc.select(transformNodes, r=True) + creasePlusMirrorCont(transformStr) + + +class CpBoolOpStat: + + title = 'boolOp' + + unionStr = 'union' + diffStr = 'difference' + intersecStr = 'intersect' + + +def cPboolOpAttrs(nodeString, boolNodeStr): + + mainAttrName = CpBoolOpStat.title + + unionStr = CpBoolOpStat.unionStr + diffStr = CpBoolOpStat.diffStr + intersecStr = CpBoolOpStat.intersecStr + + if mc.attributeQuery(mainAttrName, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + mainAttrName) + + mc.addAttr( + nodeString, + ln=mainAttrName, + k=True, + at='enum', + en=unionStr + '=1:' + diffStr + '=2:' + intersecStr + '=3', + dv=2, + ) + + mc.connectAttr(nodeString + '.' + mainAttrName, boolNodeStr + '.operation') + + +# creasePlusBoolOpCont("polySurface1") + + +def creasePlusBoolOpCont(nodeString): + + crep.global_cPboolOpCtxStr + + mainAttrName = CpBoolOpStat.title + + cur = None + if mc.currentCtx() == crep.global_cPboolOpCtxStr: + cur = crep.cPboolOpIterIncVal() + else: + cur = crep.cPboolOpIterSetVal(0) + + mc.dragAttrContext(crep.global_cPboolOpCtxStr, e=True, reset=True) + + if cur == 0: + mc.setAttr(nodeString + '.' + mainAttrName, 2) + elif cur == 1: + mc.setAttr(nodeString + '.' + mainAttrName, 1) + elif cur == 2: + mc.setAttr(nodeString + '.' + mainAttrName, 3) + + mc.setToolTo(crep.global_cPboolOpCtxStr) + + +def cPhasBoolHistory(shape): + dummy = om.MSelectionList() + dummy.add(shape) + dagFn = om.MFnDagNode(dummy.getDependNode(0)) + dagFn.setObject(dagFn.parent(0)) + transfrmStr = dagFn.partialPathName() + + if mc.attributeQuery(CpBoolOpStat.title, node=transfrmStr, ex=True): + destPlugs = mc.connectionInfo( + transfrmStr + '.' + CpBoolOpStat.title, dfs=True) + for plugStr in destPlugs: + if mc.nodeType(cPplugNode(plugStr)) == 'polyCBoolOp': + return True + else: + + return False + + return False + + +# @mayaUndoRun +def creasePlusToggleBoolGhost(): + global global_cPboolNamespace + vs = mc.ls(global_cPboolNamespace + ":*", o=True, v=True) + ivs = mc.ls(global_cPboolNamespace + ":*", o=True, iv=True) + + if (len(vs)): + mc.hide(vs) + elif (len(ivs)): + mc.showHidden(ivs) + + +@mayaUndoRun +def creasePlusBool(keepOperands=False): + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelMesh) + + crep.global_cPboolOpCtxStr + ctxActive = mc.currentCtx() == crep.global_cPboolOpCtxStr + + hasBoolHistory = cPhasBoolHistory(sel[0]) + + if ctxActive and hasBoolHistory or len(sel) == 1 and hasBoolHistory: + curTransfrm = mc.listRelatives( + sel[0], + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + creasePlusBoolOpCont(curTransfrm) + return + elif len(sel) < 2: + raise crepexcept.cPexcept(crep.CpMsg.kSelLeastTwoMesh) + + if keepOperands: + for curShape in sel[1:]: + mc.setAttr(curShape + '.visibility', True) + mc.setAttr(curShape + '.hiddenInOutliner', False) + mc.setAttr(curShape + '.intermediateObject', False) + mc.setAttr(curShape + '.overrideShading', True) + mc.setAttr(curShape + '.overrideColor', 0) + mc.setAttr(curShape + '.overrideEnabled', False) + sel[1:] = mc.listRelatives( + mc.duplicate(sel[1:], renameChildren=True), + children=True, + fullPath=True, + noIntermediate=False, + typ='mesh') + + newObj = mc.polyCBoolOp( + sel, + useCarveBooleans=True, + classification=1, + op=2, + preserveColor=False, + ch=True, + ) + + if mc.nodeType(newObj[0]) == 'mesh': + newObj[0] = mc.listRelatives( + newObj[0], + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + + dummy = om.MSelectionList() + dummy.add(newObj[1]) + + depFn = om.MFnDependencyNode(dummy.getDependNode(0)) + allPlugs = depFn.getConnections() + + inShapeStrings = set() + fstShapeStr = None + for i in range(len(allPlugs)): + curPlug = allPlugs[i] + + if not curPlug.isDestination: + continue + + srcPlug = mc.connectionInfo(curPlug.name(), sfd=True) + srcShapeStr = cPplugNode(srcPlug) + if mc.nodeType(srcShapeStr) == 'mesh': + if fstShapeStr == None: + fstShapeStr = srcShapeStr + inShapeStrings.add(srcShapeStr) + + global global_cPboolNamespace + + for curShape in inShapeStrings: + + # curShape = next(iter(inShapeStrings)) + + if curShape == fstShapeStr: + continue + + # def creasePlusBool() + + curTransfrm = mc.listRelatives( + curShape, + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + + mc.setAttr(curShape + '.visibility', True) + mc.setAttr(curShape + '.hiddenInOutliner', False) + mc.setAttr(curShape + '.intermediateObject', False) + mc.setAttr(curShape + '.overrideEnabled', True) + mc.setAttr(curShape + '.overrideShading', False) + mc.setAttr(curShape + '.overrideColor', 4) + mc.xform(curShape,cp=True) + + if not ((global_cPboolNamespace + ":") in curTransfrm): + curTransfrm = cPaddToMayaNamespace( + curTransfrm, global_cPboolNamespace + ":" + + curTransfrm[curTransfrm.rfind("|") + 1:]) + + mc.setAttr(curTransfrm + '.visibility', True) + + cPboolOpAttrs(newObj[0], newObj[1]) + creasePlusBoolOpCont(newObj[0]) + mc.select(newObj[0], r=True) + + # mel.eval("AEdagNodeCommonRefreshOutliners();") + + if crep.getmayaver().num > 2016: + mel.eval( + 'attributeEditorVisibilityStateChange(`workspaceControl -q -visible AttributeEditor`, "");' + ) + + +@mayaUndoRun +def creasePlusPanelBool(): + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + # + + if len(sel) < 2: + raise crepexcept.cPexcept(crep.CpMsg.kSelLeastTwoMesh) + + dupl = mc.duplicate(sel, renameChildren=True) + + newObj = mc.polyCBoolOp( + sel, + useCarveBooleans=True, + classification=1, + op=2, + preserveColor=False, + ch=True, + )[0] + newObj1 = mc.polyCBoolOp( + dupl, + useCarveBooleans=True, + classification=1, + op=3, + preserveColor=False, + ch=True, + )[0] + mc.select([newObj, newObj1], r=True) + mc.xform(mc.ls(sl=True),cp=True) + # mc.polyUnite() + + +class CpHBevelStat: + + offsetStr = 'hOffset' + divStr = 'hDivisions' + miterStr = 'hMitering' + + +def cPhBevelAttrs(nodeString, bevelNodeStr): + + offsetStr = CpHBevelStat.offsetStr + divStr = CpHBevelStat.divStr + miterStr = CpHBevelStat.miterStr + + + if crep.getmayaver().num > 2016 or (crep.getmayaver().num == 2016 and crep.getmayaver().extnum == 2): + if mc.attributeQuery(offsetStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + offsetStr) + + if mc.attributeQuery(divStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + divStr) + + if mc.attributeQuery(miterStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + miterStr) + + mc.addAttr( + nodeString, + ln=offsetStr, + k=True, + at='doubleLinear', + hnv=True, + min=0, + dv=0, + ) + mc.addAttr( + nodeString, + ln=divStr, + k=True, + at='long', + hnv=True, + min=0, + dv=1, + ) + mc.addAttr( + nodeString, + ln=miterStr, + k=True, + at='enum', + en='Auto=0:Star=2:Round=3', + dv=0, + ) + + mc.connectAttr(nodeString + '.' + offsetStr, bevelNodeStr + '.offset') + mc.connectAttr(nodeString + '.' + divStr, bevelNodeStr + '.segments') + mc.connectAttr(nodeString + '.' + miterStr, bevelNodeStr + '.mitering') + else: + if mc.attributeQuery(offsetStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + offsetStr) + + if mc.attributeQuery(divStr, node=nodeString, ex=True): + mc.deleteAttr(nodeString + '.' + divStr) + + mc.addAttr( + nodeString, + ln=offsetStr, + k=True, + at='doubleLinear', + hnv=True, + min=0, + dv=0, + ) + mc.addAttr( + nodeString, + ln=divStr, + k=True, + at='long', + hnv=True, + min=0, + dv=1, + ) + + mc.connectAttr(nodeString + '.' + offsetStr, bevelNodeStr + '.offset') + mc.connectAttr(nodeString + '.' + divStr, bevelNodeStr + '.segments') + +def cPhasHBevelHistory(shape): + dummy = om.MSelectionList() + dummy.add(shape) + dagFn = om.MFnDagNode(dummy.getDependNode(0)) + dagFn.setObject(dagFn.parent(0)) + transfrmStr = dagFn.partialPathName() + + if mc.attributeQuery(CpHBevelStat.offsetStr, node=transfrmStr, ex=True): + destPlugs = mc.connectionInfo( + transfrmStr + '.' + CpHBevelStat.offsetStr, dfs=True) + for plugStr in destPlugs: + if mc.nodeType(cPplugNode(plugStr)) == 'polyBevel3': + return True + else: + + return False + + return False + + +# creasePlusBool(keepOperands =True) +# creasePlusHBevelCont("polySurface1") +# creasePlusHBevelCont("pCube1") + + +def creasePlusHBevelCont(nodeString): + + crep.global_hBevelCtxStr + + offsetStr = CpHBevelStat.offsetStr + divStr = CpHBevelStat.divStr + + cur = None + if mc.currentCtx() == crep.global_hBevelCtxStr: + cur = crep.cPhBevelIterIncVal() + else: + cur = crep.cPhBevelIterSetVal(0) + + mc.dragAttrContext(crep.global_hBevelCtxStr, e=True, reset=True) + if cur == 0: + mc.dragAttrContext( + crep.global_hBevelCtxStr, e=True, ct=nodeString + '.' + offsetStr) + elif cur == 1: + mc.dragAttrContext( + crep.global_hBevelCtxStr, e=True, ct=nodeString + '.' + divStr) + + mc.setToolTo(crep.global_hBevelCtxStr) + + +@mayaUndoRun +def creasePlusBakeHBL(): + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSel) + + mc.setToolTo('moveSuperContext') + + + beveliTransforms = [] + for shapeStr in sel: + (cpheNode, bevelNode, beveli)= cPisbevelCage(shapeStr) + if cpheNode and bevelNode and beveli: + beveliTrans = mc.listRelatives(beveli, parent=True, noIntermediate=False, typ='transform')[0] + mc.delete(beveliTrans + '.translate', icn=True) + mc.delete(beveliTrans + '.rotate' , icn=True) + mc.delete(beveliTrans + '.scale' , icn=True) + mc.delete(beveli, ch=True) + mc.select(beveli,r=True) + cPcleanAttrs() + beveliTransforms.append(beveliTrans) + try: + mc.delete(shapeStr) + except: + pass + else: + (cpheNode, bevelNode, beveli, beveliCage) = cPhasbevelCage(shapeStr) + if cpheNode and bevelNode and beveli and beveliCage: + beveliTrans = mc.listRelatives(beveli, parent=True, noIntermediate=False, typ='transform')[0] + mc.delete(beveliTrans + '.translate', icn=True) + mc.delete(beveliTrans + '.rotate' , icn=True) + mc.delete(beveliTrans + '.scale' , icn=True) + mc.delete(beveli, ch=True) + mc.select( beveli,r=True) + cPcleanAttrs() + beveliTransforms.append(beveliTrans) + try: + mc.delete(beveliCage) + except: + pass + + if len(beveliTransforms) : + mc.select(beveliTransforms, r=True) + else: + mc.select( cl=True) + +@mayaUndoRun +def creasePlusHBevel(): + + sel = om.MGlobal.getActiveSelectionList() + + if sel.length() == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSel) + + crep.global_hBevelCtxStr + ctxActive = mc.currentCtx() == crep.global_hBevelCtxStr + + selIt = om.MItSelectionList(sel) + + principalObjIdx = None + transformNodes = [] + while not selIt.isDone(): + + (shape, __, edges, faces) = crep.cPgetShapeAndCoStrings(selIt) + + if not shape: + selIt.next() + continue + + shapeStr = shape.partialPathName() + + (cpheNode, bevelNode, beveli)= cPisbevelCage(shapeStr) + if cpheNode and bevelNode and beveli: + creasePlusHBevelCont( + mc.listRelatives( + beveli, + parent=True, + noIntermediate=False, + typ='transform')[0] + ) + return + + + dagFn = om.MFnDagNode(shape) + dagFn.setObject(dagFn.parent(0)) + transfrmStr = dagFn.partialPathName() + transformNodes.append(transfrmStr) + + if ctxActive and cPhasHBevelHistory(shapeStr): + creasePlusHBevelCont(transfrmStr) + return + else: + ctxActive = False + + edgeStrings = None + + if faces: + edgeStrings = crep.cPfaceToHardEdgeStrings(shape, faces) + elif edges: + + edgeStrings = crep.cPedgeToStrings(shape, edges) + else: + + edgeStrings = crep.cPhardEdgesStrings(shape) + + # + + if len(edgeStrings) == 0: + selIt.next() + continue + elif principalObjIdx == None: + principalObjIdx = len(transformNodes) - 1 + + # mc.polyMergeVertex(transfrmStr, d=0.015, am=False) + nname = 'hBevel' + nname = cPdoPolyBevel(edgeStrings, nname) + + cPhBevelAttrs(transfrmStr, nname) + + selIt.next() + + # set selection + + mc.select(transformNodes, r=True) + + # + + if principalObjIdx != None: + creasePlusHBevelCont(transformNodes[principalObjIdx]) + else: + raise crepexcept.cPexcept(crep.CpMsg.kNoHardEdges) + + +def cPplugNode(plugStr): + return plugStr[:plugStr.find('.')] + + +def cPhasbevelCage(shpStr): + + cpheNode = None + bevelNode = None + beveli = None + beveliCage = None + + hists = mc.listHistory(shpStr , bf=False, f=False, lf=True) + + for n in hists: + if mc.nodeType(n) == 'polyBevel3' and bevelNode == None: + bevelNode = n + elif mc.nodeType(n) == CreasePlusNodes.CpHeIds.kNodeName and cpheNode == None: + cpheNode= n + if cpheNode and bevelNode: + break + + if cpheNode and bevelNode: + beveli = shpStr + + if cpheNode and bevelNode and beveli: + plugs = mc.connectionInfo(bevelNode + '.inputPolymesh', sfd=True) + if isinstance(plugs, str) and mc.nodeType(cPplugNode(plugs)) == 'mesh': + beveliCage = cPplugNode(plugs) + else: + for plugStr in plugs: + if mc.nodeType(cPplugNode(plugStr)) == 'mesh': + beveliCage = cPplugNode(plugStr) + break + + return (cpheNode, bevelNode, beveli, beveliCage) + + + +def cPisbevelCage(cageshpstr): + + cpheNode = None + bevelNode = None + beveli = None + + plugs = mc.connectionInfo(cageshpstr + ".outMesh", dfs=True) + + if isinstance(plugs, str) and mc.nodeType(cPplugNode(plugs)) == CreasePlusNodes.CpHeIds.kNodeName: + cpheNode = cPplugNode(plugs) + else: + for plugStr in plugs: + if mc.nodeType(cPplugNode(plugStr)) == CreasePlusNodes.CpHeIds.kNodeName: + cpheNode = cPplugNode(plugStr) + break + + if cpheNode: + hists = mc.listHistory(cpheNode, bf=False, f=True, lf=True) + + for n in hists: + if mc.nodeType(n) == 'polyBevel3': + bevelNode = n + break + beveli = hists[-1:][0] + if mc.nodeType(beveli) != 'mesh': + beveli = None + + return (cpheNode, bevelNode, beveli) + + + +def creasePlusHBevelLiveCmd(): + global global_CP_Nodes_pluginstr + + # mel.eval("print(" + global_CP_Nodes_pluginstr + " ;") + + if not mc.pluginInfo(global_CP_Nodes_pluginstr, q=True, loaded=True): + try: + mc.loadPlugin(cPmainDir() + global_CP_Nodes_pluginstr + '.py') + except: + raise crepexcept.cPexcept(crep.CpMsg.kcPnodePluginNotLoaded) + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + if len(sel) != 1: + raise crepexcept.cPexcept(crep.CpMsg.kSelOneMesh) + sel = sel[0] + mc.select(sel, r=True) + + (cpheNode, bevelNode, beveli)= cPisbevelCage(sel) + if cpheNode and bevelNode and beveli: + creasePlusHBevelCont( + mc.listRelatives( + beveli, + parent=True, + noIntermediate=False, + typ='transform')[0] + ) + return + + mc.xform(sel,cp=True) + creasePlusHBevel() + srcPlugs = mc.connectionInfo(sel + '.inMesh', sfd=True) + bevelNode = None + if isinstance(srcPlugs, str) and mc.nodeType( + cPplugNode(srcPlugs)) == 'polyBevel3': + bevelNode = cPplugNode(srcPlugs) + else: + for plugStr in srcPlugs: + if mc.nodeType(cPplugNode(plugStr)) == 'polyBevel3': + bevelNode = cPplugNode(plugStr) + break + srcPlugs = mc.connectionInfo(bevelNode + '.inputPolymesh', sfd=True) + + sourceMesh = None + if isinstance(srcPlugs, str): + plugStr = srcPlugs + if mc.nodeType(cPplugNode(srcPlugs)) == 'mesh': + sourceMesh = cPplugNode(srcPlugs) + else: + plugStr = srcPlugs[0] + if mc.nodeType(cPplugNode(plugStr)) == 'mesh': + sourceMesh = cPplugNode(plugStr) + + if not sourceMesh: + sourceMesh = mc.createNode('mesh') + mc.disconnectAttr(plugStr, bevelNode + '.inputPolymesh') + mc.connectAttr(plugStr, sourceMesh + '.inMesh') + mc.connectAttr(sourceMesh + '.outMesh', bevelNode + '.inputPolymesh') + + sourcetrans = mc.listRelatives( + sourceMesh, + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + seltrans = mc.listRelatives( + sel, + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + mc.xform(sourcetrans,cp=True) + + + mc.connectAttr(seltrans + '.translate', sourcetrans + '.translate') + mc.connectAttr(seltrans + '.rotate', sourcetrans + '.rotate') + mc.connectAttr(seltrans + '.scale', sourcetrans + '.scale') + mc.disconnectAttr(seltrans + '.translate', sourcetrans + '.translate') + mc.disconnectAttr(seltrans + '.rotate', sourcetrans + '.rotate') + mc.disconnectAttr(seltrans + '.scale', sourcetrans + '.scale') + else: + mc.setAttr(sourceMesh + '.visibility', True) + mc.setAttr(sourceMesh + '.hiddenInOutliner', False) + mc.setAttr(sourceMesh + '.intermediateObject', False) + newtrans = mc.createNode('transform') + seltrans = mc.listRelatives( + sel, + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + + mc.connectAttr(seltrans + '.translate', newtrans + '.translate') + mc.connectAttr(seltrans + '.rotate', newtrans + '.rotate') + mc.connectAttr(seltrans + '.scale', newtrans + '.scale') + mc.disconnectAttr(seltrans + '.translate', newtrans + '.translate') + mc.disconnectAttr(seltrans + '.rotate', newtrans + '.rotate') + mc.disconnectAttr(seltrans + '.scale', newtrans + '.scale') + + mc.parent(sourceMesh, newtrans, shape=True, relative=True) + + mc.setAttr(sourceMesh + '.overrideEnabled', True) + mc.setAttr(sourceMesh + '.overrideShading', False) + mc.setAttr(sourceMesh + '.overrideColor', 5) + + idsnode = mc.createNode(CreasePlusNodes.CpHeIds.kNodeName) + mc.connectAttr(sourceMesh + '.outMesh', idsnode + '.i') + mc.connectAttr(idsnode + '.cl', bevelNode + '.inputComponents') + + sourcetrans = mc.listRelatives( + sourceMesh, + parent=True, + fullPath=True, + noIntermediate=False, + typ='transform')[0] + seltrans = mc.listRelatives( + sel, parent=True, fullPath=True, noIntermediate=False, + typ='transform')[0] + mc.connectAttr(sourcetrans + '.translate', seltrans + '.translate') + mc.connectAttr(sourcetrans + '.rotate', seltrans + '.rotate') + mc.connectAttr(sourcetrans + '.scale', seltrans + '.scale') + + mc.setAttr(seltrans + '.' + CpHBevelStat.offsetStr, 0.05) + mc.select(sourcetrans, r=True) + +@mayaUndoRun +def creasePlusHBevelLive(): + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kSelMesh) + + for i in range(len(sel)): + mc.select(sel[i],r=True) + creasePlusHBevelLiveCmd() + if i != len(sel)-1: + mc.setToolTo("moveSuperContext") + + +def cPautoCenterpiv(): + if len(mc.ls(sl=True)): + mc.xform(cp=True) + +@mayaUndoRun +def creasePlusDrawCurve(): + mc.setToolTo(crep.global_cPcurveCtxStr) + mc.scriptJob(cu=True, ro=True, e=['PostToolChanged', cPautoCenterpiv]) + +@mayaUndoRun +def creasePlusAttachCurve(): + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + + if len(selCurve) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kSelLeastTwoCurve) + + newCurve = mc.attachCurve( + selCurve, + ch=False, + rpo=True, + kmk=True, + m=0, + bb=0.5, + bki=False, + p=0.1, + )[0] + + # mc.delete(newCurve, ch=True) + + mc.select(newCurve, r=True) + + +@mayaUndoRun +def creasePlusCloseCurve(): + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + + if len(selCurve) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelCurve) + + for shapeStr in selCurve: + + curveForm = mc.getAttr(shapeStr + '.form') + + if mc.getAttr(shapeStr + '.degree') == 3: + + # mc.closeCurve( rpo=True) + + if curveForm == 0: + mc.closeCurve( + shapeStr, + ch=False, + ps=0, + rpo=True, + bb=0.5, + bki=True, + p=0.1, + ) + else: + if curveForm == 0: + mc.closeCurve( + shapeStr, + ch=False, + ps=1, + rpo=True, + bb=0.5, + bki=False, + p=0.1, + ) + + mc.select( + mc.listRelatives( + selCurve, parent=True, fullPath=True, typ='transform'), + r=True) + + +@mayaUndoRun +def creasePlusCurveIntersect(): + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + + if len(selCurve) < 2: + raise crepexcept.cPexcept(crep.CpMsg.kSelLeastTwoCurve) + + mel.eval('cutCurvePreset(0,1,0.01,6,0,1,0,1,2);') + mc.select(cl=True) + + +@mayaUndoRun +def creasePlusCurveDoubleCvs(): + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + + if len(selCurve) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelCurve) + + for shapeStr in selCurve: + dummy = om.MSelectionList() + dummy.add(shapeStr) + curveFn = om.MFnNurbsCurve(dummy.getDependNode(0)) + deg = curveFn.degree + + # mc.select(mc.listRelatives(shapeStr,parent=True,fullPath=True, typ="transform")[0], r=True) + + mel.eval('selectCurveCV all;') + + # newNumCv = (len(mc.ls(sl=True ,fl=True)) * 2) + + mc.rebuildCurve( + shapeStr, + ch=True, + rpo=True, + rt=0, + end=1, + kr=2, + kcp=False, + kep=True, + kt=False, + s=curveFn.numSpans * 2, + d=deg, + tol=0.01, + ) + + mc.select(mc.listRelatives(selCurve, parent=True, fullPath=True), r=True) + + +def cPsphereSegIntersect( + q, + v, + p, + r, +): + a = (v - q).length()**2 + b = 2 * ((v - q) * (q - p)) + c = p.length()**2 + q.length()**2 - 2 * (p * q) - r**2 + + sqr = b**2 - 4 * a * c + if sqr < 0 or a == 0: + return None + s = (-b + math.sqrt(sqr)) / (2 * a) + t = None + if sqr > 0: + t = (-b - math.sqrt(sqr)) / (2 * a) + + # intp = q + (s*(v-q)) + + return (1 * s, 1 * t) + + +def cPsegInsideSphere( + q, + v, + p, + r, +): + a = (q - p).length() + b = (v - p).length() + + if a < r and b < r: + return True + else: + return False + + +# do not use + + +def creasePlusCurveRebuild(numSeg, numCv=None): + + if numCv != None: + numSeg = numCv - 1 + + if numSeg < 1: + raise crepexcept.cPexcept(crep.CpMsg.kInvalidFuncArgs) + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + + if len(selCurve) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelCurve) + + dummy = om.MSelectionList() + dummy.add(selCurve[0]) + curveFn = om.MFnNurbsCurve(dummy.getDependNode(0)) + polyline = curveFn.cvPositions() + polyline = [om.MVector(pt) for pt in polyline] + + pllen = float(0) + for i in range(len(polyline) - 1): + p0 = polyline[i] + p1 = polyline[i + 1] + pllen += (p1 - p0).length() + + param = pllen / float(numSeg) + + # numCv = numSeg+1 + + i = 0 + newPolyline = [] + lastPt = polyline[i] + newPolyline.append(1 * lastPt) + while len(newPolyline) < numSeg and i < len(polyline) - 1: + while len(newPolyline) < numSeg and i < len(polyline) - 1: + + p0 = polyline[i] + p1 = polyline[i + 1] + + if cPsegInsideSphere(p0, p1, lastPt, 1 * param): + i += 1 + continue + + s = cPsphereSegIntersect(p0, p1, lastPt, 1 * param)[0] + + # respt = p0 + s * (p1-p0) + + if s == None: + i += 1 + continue + + if 0 <= s <= 1: + lastPt = p0 + s * (p1 - p0) + newPolyline.append(1 * lastPt) + break + + i += 1 + + newPolyline.append(1 * polyline[len(polyline) - 1]) + for pt in newPolyline: + mc.spaceLocator(p=[pt.x, pt.y, pt.z]) + + +# cPhasCurveBevelHistory("curveShape4") + + +def cPhasCurveBevelHistory(shape): + + # shape = "curveShape2" + + srcPlugs = mc.connectionInfo(shape + '.create', sfd=True) + if isinstance(srcPlugs, str) and mc.nodeType( + cPplugNode(srcPlugs)) == CreasePlusNodes.CpCurveBevel.kNodeName: + return cPplugNode(srcPlugs) + else: + for plugStr in srcPlugs: + if mc.nodeType(cPplugNode( + plugStr)) == CreasePlusNodes.CpCurveBevel.kNodeName: + return cPplugNode(plugStr) + + return None + + +def creasePlusCurveBevelCont(nodeString): + + # crep.global_cPcurveBevelCtxStr + + cur = None + if mc.currentCtx() == crep.global_cPcurveBevelCtxStr: + cur = crep.cPcurveBevelIterIncVal() + else: + cur = crep.cPcurveBevelIterSetVal(0) + + mc.dragAttrContext(crep.global_cPcurveBevelCtxStr, e=True, reset=True) + if cur == 0: + mc.dragAttrContext( + crep.global_cPcurveBevelCtxStr, e=True, ct=nodeString + '.offset') + elif cur == 1: + mc.dragAttrContext( + crep.global_cPcurveBevelCtxStr, + e=True, + ct=nodeString + '.segments') + + mc.setToolTo(crep.global_cPcurveBevelCtxStr) + + +@mayaUndoRun +def creasePlusCurveBevelCmd(): + + global global_CP_Nodes_pluginstr + if not mc.pluginInfo(global_CP_Nodes_pluginstr, q=True, loaded=True): + try: + mc.loadPlugin(cPmainDir() + global_CP_Nodes_pluginstr + '.py') + except: + raise crepexcept.cPexcept(crep.CpMsg.kcPnodePluginNotLoaded) + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + if len(selCurve) != 1: + raise crepexcept.cPexcept(crep.CpMsg.kWorksForOne) + + selCurve = selCurve[0] + + ctxActive = mc.currentCtx() == crep.global_cPcurveBevelCtxStr + node = None + try: + node = cPhasCurveBevelHistory(selCurve) + except: + node = None + + if ctxActive and node != None: + creasePlusCurveBevelCont(node) + return + + selCvs = mc.filterExpand(mc.ls(sl=True), sm=28, ex=True) + if selCvs == None: + raise crepexcept.cPexcept(crep.CpMsg.kSelCurveCv) + if len(selCvs) == 0: + if node != None: + creasePlusCurveBevelCont(node) + return + else: + raise crepexcept.cPexcept(crep.CpMsg.kSelCurveCv) + + node = mc.createNode(CreasePlusNodes.CpCurveBevel.kNodeName) + newcurveShape = mc.createNode('nurbsCurve') + + mc.setAttr( + node + '.cvs', + len(selCvs), + type='componentList', + *[curcv[curcv.find('.') + 1:] for curcv in selCvs]) + + mc.connectAttr(selCurve + '.worldSpace[0]', node + '.inc') + mc.connectAttr(node + '.out', newcurveShape + '.create') + + # + + creasePlusCurveBevelCont(node) + + +@mayaUndoRun +def creasePlusCurveToPolyCmd(): + + global global_CP_Nodes_pluginstr + if not mc.pluginInfo(global_CP_Nodes_pluginstr, q=True, loaded=True): + try: + mc.loadPlugin(cPmainDir() + global_CP_Nodes_pluginstr + '.py') + except: + raise crepexcept.cPexcept(crep.CpMsg.kcPnodePluginNotLoaded) + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + if len(selCurve) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelCurve) + + for shapeStr in selCurve: + node = mc.createNode(CreasePlusNodes.CpCurveToPoly.kNodeName) + npoly = mc.createNode('mesh') + + mc.connectAttr(shapeStr + '.worldSpace[0]', node + '.inc') + mc.connectAttr(node + '.out', npoly + '.inMesh') + + mc.sets(npoly, e=1, forceElement='initialShadingGroup') + + mc.select( + mc.listRelatives(npoly, parent=True, fullPath=True, typ='transform'), + r=True) + + +@mayaUndoRun +def creasePlusCurveSlice(): + + selCurve = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + selMesh = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(selCurve) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelCurve) + + if len(selMesh) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelMesh) + + dplane = crep.cPcameraDominantPlane() + + curveDupl = mc.listRelatives( + mc.duplicate(selCurve[0], renameChildren=True), + children=True, + fullPath=True, + typ='nurbsCurve')[0] + + dagFn = om.MFnDagNode() + dummy = om.MSelectionList() + + dummy.add(curveDupl) + dagFn.setObject(dummy.getDependNode(0)) + + curveBb = dagFn.boundingBox + curvemin = curveBb.min + curvemax = curveBb.max + + # print((curvemin) + + curveWhd = (abs(curvemax.x - curvemin.x), abs(curvemax.y - curvemin.y), + abs(curvemax.z - curvemin.z)) + + curveMaxOffset = 0 + if curveMaxOffset < curveWhd[0]: + curveMaxOffset = curveWhd[0] + if curveMaxOffset < curveWhd[1]: + curveMaxOffset = curveWhd[1] + if curveMaxOffset < curveWhd[2]: + curveMaxOffset = curveWhd[2] + + curveDepth = 0 + + i = 1 + finalSel = [] + for shapeStr in selMesh: + + # shapeStr = "pCubeShape1" + + dummy.add(shapeStr) + dagFn.setObject(dummy.getDependNode(i)) + + bb = dagFn.boundingBox + bbmin = bb.min + bbmax = bb.max + bbWhd = (abs(bbmax.x - bbmin.x), abs(bbmax.y - bbmin.y), + abs(bbmax.z - bbmin.z)) + + bbMaxOffset = 0 + if bbMaxOffset < bbWhd[0]: + bbMaxOffset = bbWhd[0] + if bbMaxOffset < bbWhd[1]: + bbMaxOffset = bbWhd[1] + if bbMaxOffset < bbWhd[2]: + bbMaxOffset = bbWhd[2] + + curveDepth = bbMaxOffset + curveDepth += curveMaxOffset + curveDepth * 0.1 + + dirIdx = None + extrudeVec = None + axisCenter = None + if dplane == 'x': + dirIdx = 0 + extrudeVec = [1, 0, 0] + axisCenter = bb.center.x + elif dplane == 'y': + dirIdx = 1 + extrudeVec = [0, 1, 0] + axisCenter = bb.center.y + elif dplane == 'z': + dirIdx = 2 + extrudeVec = [0, 0, 1] + axisCenter = bb.center.z + + # + + mel.eval( + 'nurbsToPolygonsPref -f 3 -ucr 0 -uch 0 -pt 0 -m 0 -mt 0.1 -mrt 0;' + ) + + mc.optionVar(iv=('extrudeDirectionType', dirIdx)) + mc.optionVar(fv=('extrudeLength', curveDepth)) + + nsurface = mc.extrude( + curveDupl, + ch=False, + rn=False, + po=1, + et=0, + upn=0, + direction=extrudeVec, + length=curveDepth, + ro=0, + sc=1, + dl=1, + )[0] + + if mc.getAttr(curveDupl + '.form') == 1: + mel.eval('polyCloseBorder -ch 0 ' + nsurface) + + mc.xform(nsurface, cp=True) + mc.delete(nsurface, ch=True) + + if dplane == 'x': + mel.eval('move -rpr -moveX ' + str(axisCenter) + ';') + elif dplane == 'y': + mel.eval('move -rpr -moveY ' + str(axisCenter) + ';') + elif dplane == 'z': + mel.eval('move -rpr -moveZ ' + str(axisCenter) + ';') + + mc.select([shapeStr, nsurface], r=True) + creasePlusPanelBool() + finalSel += mc.ls(sl=True) + i += 1 + + # creasePlusCurveSlice() + + mc.delete( + mc.listRelatives( + curveDupl, parent=True, fullPath=True, typ='transform')) + + # mc.delete(finalSel, ch=True) # delete final sel history + + mc.select(finalSel, r=True) + + +def cPdoUnfold(shapeStr): + if crep.getmayaver().num > 2016: + mel.eval( + 'u3dUnfold -ite 10 -p 1 -bi 1 -tf 1 -ms 1024 -rs 0 ' + shapeStr + ';') + else: + mel.eval( + 'Unfold3D -u -ite 10 -p 1 -bi 1 -tf 1 -ms 1024 -rs 0 ' + shapeStr + ';') + + +@mayaUndoRun +def creasePlusMakeUv(): + + if not mc.pluginInfo('Unfold3D', q=True, loaded=True): + raise crepexcept.cPexcept("\'Unfold3D\' plugin must be loaded.\n") + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kSelMesh) + + for shapeStr in sel: + + faces = mc.polyListComponentConversion(shapeStr, tf=True) + mc.polyProjection( + faces, + ch=True, + t='planar', + ibd=True, + kir=True, + md='c', + ) + + edgeStrings = crep.cPhardEdgesStrings(shapeStr) + + if len(edgeStrings) == 0: + raise crepexcept.cPexcept( + shapeStr + ' has no hard edges, UV generation failed.\n') + + mc.select(edgeStrings, r=True) + mc.polyMapCut(ch=True) + + cPdoUnfold(shapeStr) + + + mc.select( + mc.listRelatives(parent=True, fullPath=True, typ='transform'), r=True) + if len(mc.ls(sl=True)): + print('DONE!') + + +# SUBD TOOLS + + +def cPapplyCrease(sel, val, clearcrease=False): + if clearcrease: + mc.polyCrease(sel, op=2) + mc.polyCrease(sel, v=val) + + +def cPdisplayNotSmooth(shapeStr): + mc.setAttr(shapeStr + '.displaySmoothMesh', 0) + + +def cPdisplaySmooth(shapeStr): + mc.setAttr(shapeStr + '.displaySmoothMesh', 2) + + +def cPsmoothGroupsSubDAttrs(shapeStr, nodestr1, nodestr2): + + # shapeStr = "pCubeShape1" + + transfrmStr = mc.listRelatives( + shapeStr, parent=True, fullPath=True, typ='transform')[0] + mc.addAttr( + transfrmStr, + k=True, + ln='baseDiv', + at='short', + dv=2, + hnv=True, + min=0, + softMaxValue=5, + ) + mc.addAttr( + transfrmStr, + k=True, + ln='divisions', + at='short', + dv=1, + hnv=True, + min=0, + softMaxValue=5, + ) + mc.connectAttr(transfrmStr + '.baseDiv', nodestr1 + '.divisions') + mc.connectAttr(transfrmStr + '.divisions', nodestr2 + '.divisions') + + +def cPshowCreaseEd(): + mel.eval( + 'python ("from maya.app.general import creaseSetEditor; creaseSetEditor.showCreaseSetEditor()");' + ) + + +@mayaUndoRun +def creasePlusNocrease(): + objsel = mc.filterExpand(mc.ls(sl=True), ex=True, sm=12) + if objsel != None: + mc.polyCrease(op=2) + else: + mc.polyCrease(mc.ls(sl=True), op=1) + + +@mayaUndoRun +def creasePlusSmoothGroupsSubD(): + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kSelMesh) + + mel.eval('LowQualityDisplay;') + + for shapeStr in sel: + edgeStrings = crep.cPhardEdgesStrings(shapeStr) + if len(edgeStrings) == 0: + continue + + mc.polyCrease(shapeStr, op=2) + mc.polyCrease(edgeStrings, v=5.0) + + nnode1 = mc.polySmooth(shapeStr)[0] + + mc.polyCrease(shapeStr, op=2) + + nnode2 = mc.polySmooth(shapeStr)[0] + + cPsmoothGroupsSubDAttrs(shapeStr, nnode1, nnode2) + + mc.select( + mc.listRelatives(sel, parent=True, fullPath=True, typ='transform'), + r=True) + + +@mayaUndoRun +def creasePlusSubDpreset(): + + osel = mc.ls(sl=True) + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kSelMesh) + + mc.polyOptions(dce=False) + mel.eval('LowQualityDisplay;') + + for shapeStr in sel: + smtlvl = cPsetCreaseSmoothLevel(shapeStr) + mc.setAttr(shapeStr + '.smoothLevel', 2) + smoothNode = mc.polySmooth(shapeStr)[0] + + mc.setAttr(smoothNode + '.divisions', smtlvl) + + mc.select(osel, r=True) + + +def cPupdateSmoothLvl(): + + edgesel = mc.filterExpand(mc.ls(sl=True), ex=True, sm=32) + + if not edgesel: + raise crepexcept.cPexcept(crep.CpMsg.kNoSelEdge) + + # mc.polyOptions(dce = False) + + shapeStr = mc.listRelatives( + edgesel[0], parent=True, fullPath=True, typ='mesh')[0] + + mc.setAttr(shapeStr + '.osdSmoothTriangles', 1) + + cPsetCreaseSmoothLevel(shapeStr) + + +def cPsetCreaseSmoothLevel(shapeStr): + + # shapeStr = "pCube1" + + maxv = -1.0 + vals = mc.polyCrease(shapeStr + '.e[*]', q=True, v=True) + for cur in vals: + if maxv < cur: + maxv = cur + + # newlvl = math.ceil(maxv) + + newlvl = float(int(maxv) + 1) + + if newlvl < 1: + newlvl = 1 + + mc.setAttr(shapeStr + '.smoothLevel', newlvl) + + # mc.setAttr(shapeStr + ".smoothLevel", newlvl + 1) + + return newlvl + + +@mayaUndoRun +def creasePlusWeigthTool(): + + objsel = mc.filterExpand(mc.ls(sl=True), ex=True, sm=12) + edgesel = mc.filterExpand(mc.ls(sl=True), ex=True, sm=32) + + if objsel == None and edgesel == None: + raise crepexcept.cPexcept(crep.CpMsg.kSelEdgeOrOneMesh) + + if objsel != None: + edgesel = crep.cPhardEdgesStrings( + mc.listRelatives( + objsel[0], children=True, fullPath=True, typ='mesh')[0]) + if len(edgesel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoHardEdges) + + mc.polyOptions(dce=False) + shapeStr = mc.listRelatives(edgesel[0], parent=True, fullPath=True)[0] + mc.setAttr(shapeStr + '.osdSmoothTriangles', 1) + cPdisplaySmooth(shapeStr) + + crep.global_cPcreaseCtxStr + mc.setToolTo(crep.global_cPcreaseCtxStr) + mc.scriptJob(cu=True, ro=True, e=['PostToolChanged', cPupdateSmoothLvl]) + + +@mayaUndoRun +def creasePlusPhysicalCrease(): + # sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + # if len(sel) == 0: + # raise crepexcept.cPexcept(crep.CpMsg.kSelMesh) + + # mc.select(sel, r=True) + + if mc.currentCtx() == crep.global_hBevelCtxStr: + creasePlusHBevel() + return None + + creasePlusHBevel() + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + if len(sel) == 0: + return None + + for shapestr in sel: + transfrmStr = mc.listRelatives(shapestr, parent=True, type="transform")[0] + bevelNode = None + if mc.attributeQuery(CpHBevelStat.offsetStr, node=transfrmStr, ex=True): + destPlugs = mc.connectionInfo( + transfrmStr + '.' + CpHBevelStat.offsetStr, dfs=True) + for plugStr in destPlugs: + if mc.nodeType(cPplugNode(plugStr)) == 'polyBevel3': + bevelNode = cPplugNode(plugStr) + break + + if bevelNode: + nname = 'pCrease' + nname = mc.rename(bevelNode, nname) + bevelNode = nname + if crep.getmayaver().num > 2016 or (crep.getmayaver().num == 2016 and crep.getmayaver().extnum == 2): + mc.setAttr(bevelNode + ".chamfer" , False) + else: + mc.setAttr(bevelNode + ".segments", 2) + else: + pass + + + +@mayaUndoRun +def creasePlusCreasePreset(presetnum): + + # presetnum == 1, 2, 3 + + sel = om.MGlobal.getActiveSelectionList() + + if sel.length() == 0: + raise crepexcept.cPexcept(crep.CpMsg.kNoSel) + + selIt = om.MItSelectionList(sel) + + principalObjIdx = None + transformNodes = [] + while not selIt.isDone(): + + (shape, __, edges, faces) = crep.cPgetShapeAndCoStrings(selIt) + + if not shape: + selIt.next() + continue + + shapeStr = shape.partialPathName() + dagFn = om.MFnDagNode(shape) + dagFn.setObject(dagFn.parent(0)) + transfrmStr = dagFn.partialPathName() + transformNodes.append(transfrmStr) + + edgeStrings = None + + if faces: + edgeStrings = crep.cPfaceToHardEdgeStrings(shape, faces) + elif edges: + + edgeStrings = crep.cPedgeToStrings(shape, edges) + else: + + edgeStrings = crep.cPhardEdgesStrings(shape) + + # + + if len(edgeStrings) == 0: + selIt.next() + continue + elif principalObjIdx == None: + principalObjIdx = len(transformNodes) - 1 + + resetCrease = not(faces or edges) + if presetnum == 1: + cPapplyCrease(edgeStrings, 2.0, resetCrease) + cPsetCreaseSmoothLevel(shapeStr) + elif presetnum == 2: + cPapplyCrease(edgeStrings, 3.0, resetCrease) + cPsetCreaseSmoothLevel(shapeStr) + elif presetnum == 3: + cPapplyCrease(edgeStrings, 4.0, resetCrease) + cPsetCreaseSmoothLevel(shapeStr) + elif presetnum == 0: + cPapplyCrease(edgeStrings, 0.0, resetCrease) + cPsetCreaseSmoothLevel(shapeStr) + else: + raise crepexcept.cPexcept(crep.CpMsg.kInvalidFuncArgs) + + cPdisplaySmooth(shapeStr) + selIt.next() + + # set selection + + mc.select(transformNodes, r=True) + + # + + if principalObjIdx == None: + raise crepexcept.cPexcept(crep.CpMsg.kNoHardEdges) + else: + mc.polyOptions(dce=False) + + +############### + + +@mayaUndoRun +def cPcleanAttrs(): + sel = mel.eval( + 'listRelatives -p -f `eval("listRelatives -p -f `polyListComponentConversion -tv`")`' + ) + for trans in sel: + cusattr = mc.listAttr(trans, ud=True) + if cusattr != None: + for a in cusattr: + mel.eval('deleteAttr -at ' + a + ' ' + trans + ' ;') + + +def creasePlusLastCtx(): + meshsel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + curvesel = crep.cPgetShapeStringsFromSel(om.MFn.kNurbsCurve) + + if len(meshsel): + shapeStr = meshsel[0] + trnsfrm = mc.listRelatives( + meshsel[0], parent=True, fullPath=True, typ='transform')[0] + + if cPhasHBevelHistory(shapeStr): + creasePlusHBevelCont(trnsfrm) + elif cPhasBoolHistory(shapeStr): + creasePlusBoolOpCont(trnsfrm) + elif cPhasMirrorHistory(shapeStr): + creasePlusMirrorCont(trnsfrm) + elif len(curvesel): + shapeStr = curvesel[0] + trnsfrm = mc.listRelatives( + curvesel[0], parent=True, fullPath=True, typ='transform')[0] + curvebvlnode = cPhasCurveBevelHistory(shapeStr) + if curvebvlnode != None: + creasePlusCurveBevelCont(curvebvlnode) + + +def cPcopyHBevelAttrs(srcnode, targetnode): + offsetStr = CpHBevelStat.offsetStr + divStr = CpHBevelStat.divStr + miterStr = CpHBevelStat.miterStr + + a1 = mc.getAttr(srcnode + '.' + offsetStr) + a2 = mc.getAttr(srcnode + '.' + divStr) + a3 = mc.getAttr(srcnode + '.' + miterStr) + + mc.setAttr(targetnode + '.' + offsetStr, a1) + mc.setAttr(targetnode + '.' + divStr, a2) + mc.setAttr(targetnode + '.' + miterStr, a3) + + +@mayaUndoRun +def creasePlusTransferHBevel(): + + sel = crep.cPgetShapeStringsFromSel(om.MFn.kMesh) + + if len(sel) == 0: + raise crepexcept.cPexcept(crep.CpMsg.kSelMesh) + + if not cPhasHBevelHistory(sel[0]): + raise crepexcept.cPexcept('no Hbevel history found on first object') + + seltrans = mc.listRelatives( + sel[0], parent=True, fullPath=True, typ='transform')[0] + for shapeStr in sel[1:]: + if cPhasHBevelHistory(shapeStr): + cPcopyHBevelAttrs(seltrans, + mc.listRelatives( + shapeStr, + parent=True, + fullPath=True, + typ='transform')[0]) + + +############### + + +def main(): + return None + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlusEULA.pdf b/Scripts/Modeling/Edit/CreasePlus/CreasePlusEULA.pdf new file mode 100644 index 0000000..347d4a6 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/CreasePlusEULA.pdf differ diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlusExcept.py b/Scripts/Modeling/Edit/CreasePlus/CreasePlusExcept.py new file mode 100644 index 0000000..733e6dd --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/CreasePlusExcept.py @@ -0,0 +1,6 @@ +import maya.api.OpenMaya as om + + +def cPexcept(excepstr=""): + om.MGlobal.displayError(excepstr) + return Exception(excepstr) \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlusMain.py b/Scripts/Modeling/Edit/CreasePlus/CreasePlusMain.py new file mode 100644 index 0000000..850ae0c --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/CreasePlusMain.py @@ -0,0 +1,1057 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" +_____ _____ ______ _____ ______ +/ ____| __ \| ____| /\ / ____| ____|_ +| | | |__) | |__ / \ | (___ | |__ _| |_ +| | | _ /| __| / /\ \ \___ \| __|_ _| +| |____| | \ \| |____ / ____ \ ____) | |____|_| +\_____|_| \_\______/_/ \_\_____/|______| + +""" + +from functools import partial +import math + +import os + +crease_plus_dir = os.path.dirname(os.path.abspath(__file__)).replace('\\', '/') +print(crease_plus_dir) +try: + from PySide2.QtGui import * + from PySide2.QtCore import * + from PySide2.QtWidgets import * + def whatpyside(): + return 2 + +except ImportError: + try: + from PySide.QtGui import * + from PySide.QtCore import * + def whatpyside(): + return 1 + except ImportError: + raise Exception("Couldn't import the PySide module.") + +import importlib + +# from shiboken2 import wrapInstance + +# import maya.OpenMayaUI as omui +import maya.api.OpenMaya as om +import maya.cmds as cmds + +from . import CreasePlusCore +crepcore = CreasePlusCore + +# TODO remove reloads +# crepcore = importlib.reload(crepcore) + +maya_useNewAPI = True + + +def cPgetScreenSz(): + rec = QApplication.desktop().screenGeometry() + w = rec.width() + h = rec.height() + return (w, h) + +global_cPscreensize = cPgetScreenSz() +def cPscreenSize(): + global global_cPscreensize + return global_cPscreensize + +def cPsizeRatio(w=None,h=None): + rw = None + rh = None + if w != None : + rw = float(w) / 1600 + if h != None : + rh=float(h) / 900 + + if rw != None and rh != None: + return (rw,rh) + elif rw != None: + return (rw) + elif rh != None: + return (rh) + + +def cPmayaMainWindow(): + # mayaPtr = omui.MQtUtil.mainWindow() + # mayaWindow = wrapInstance(int(mayaPtr), QWidget) + # return mayaWindow + try: + mainWindow = QApplication.activeWindow() + while True: + lastWin = mainWindow.parent() + if lastWin: + mainWindow = lastWin + else: + break + return mainWindow + except: + pass + + +# def cPmayaScriptDir(): +# return crease_plus_dir + +def cPiconDir(): + icon_dir = crease_plus_dir + '/Icons/' + return icon_dir + +# global_maya_script_dir = cmds.internalVar(usd=True) +# global_icons_dir = global_maya_script_dir + 'Icons/' + + +def icoStr(iconame): + # global global_icons_dir + # return global_icons_dir + 'crep_' + iconame + '_ico.png' + return cPiconDir() + 'crep_' + iconame + '_ico.png' + + +global_creasePlusMainUi = 'creasePlusMainUi' + +global_cPsideshapestyle1 = ''' +QWidget { +border: 0px solid #2e3234; +background: #db9456; +font-size: 0px; +border-radius: 0px; +color: #ffffff; +} +''' + +global_cPsideshapestyle2 = ''' +QWidget { +border: 0px solid #2e3234; +background: #d5703f; +font-size: 0px; +border-radius: 0px; +color: #ffffff; +} +''' + +global_cPsideshapestyle3 = ''' +QWidget { +border: 0px solid #2e3234; +background: #5ebae9; +font-size: 0px; +border-radius: 0px; +color: #ffffff; +} +''' + +global_defInfodic = { + 'toolName': 'SomeTool', + 'toolDesc': 'tool description', + 'toolHelp': 'tool help' +} + +class CreasePlusMainPage(QWidget): + def __init__(self): + super(CreasePlusMainPage, self).__init__() + self.mainLay = QVBoxLayout() + self.setLayout(self.mainLay) + self.layout().setContentsMargins(2, 2, 2, 2) + + +class CreasePlusInfopop(QDialog): + def __init__(self, parent=None): + super(CreasePlusInfopop, self).__init__(parent) + + self.setStyleSheet(''' +QWidget { +border: 2px solid #66696c; +background: #202122; +border-radius: 4px; +color: #ffffff; +} +''') + + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup + | Qt.WA_TranslucentBackground) + self.setAttribute(Qt.WA_DeleteOnClose) + self.setWindowOpacity(1) + + self.mainLay = QVBoxLayout() + self.setLayout(self.mainLay) + self.mainLay.setSpacing(1) + self.mainLay.setContentsMargins(8, 8, 8, 8) + + self.titleico = QLabel() + self.titleico.setStyleSheet(''' +QWidget { +border: 0px solid #2e3234; +background: transparent; +font-size: 14px; +border-radius: 0px; +color: #ffffff; +}''') + self.mainLay.addWidget(self.titleico) + + self.description = QLabel() + self.description.setAlignment(Qt.AlignLeft) + self.description.setStyleSheet(''' +QWidget { +border: 0px solid #2e3234; +background: #454545; +font-size: 12px; +border-radius: 4px; +color: #ffffff; +} +''') + self.mainLay.addWidget(self.description) + + def resizeEvent(self, event): + super(CreasePlusInfopop, self).resizeEvent(event) + + radius = 5.0 + painterpath = QPainterPath() + painterpath.addRoundedRect(QRectF(self.rect()), radius, radius) + maskedRegion = QRegion(painterpath.toFillPolygon().toPolygon()) + self.setMask(maskedRegion) + + +class CreasePlusBtn(QWidget): + + pressed = Signal() + global global_defInfodic + + def __init__( + self, + pmap=QPixmap(), + lbl='', + infobubble=None, + infodic=global_defInfodic, + parent=None, + ): + + super(CreasePlusBtn, self).__init__(parent) + + self.setStyleSheet(''' + QWidget {border: 0px solid #ffffff;background: #444444;border-radius: 0px;color: #ffffff; + }\nQWidget :hover {border: 0px solid #ffffff;background: #5e7876;border-radius: 3px;color: #ffffff; + }''') + + self.infoBubble = infobubble + self.infodic = infodic + + self.mainLay = QVBoxLayout() + self.setLayout(self.mainLay) + self.mainLay.setSpacing(0) + self.mainLay.setContentsMargins(2, 2, 2, 2) + + self.btn = QPushButton() + self.btn.setStyleSheet(''' + QWidget {border: 0px solid #ffffff;background: transparent;border-radius: 0px;color: #ffffff; + }''') + + self.btn.setIcon(pmap) + self.btn.setSizePolicy(QSizePolicy.Ignored,QSizePolicy.MinimumExpanding) + self.mainLay.addWidget(self.btn) + + def setPixmap(self, pmap): + self.btn.setIcon(pmap) + + def setPixmapSize(self, sz): + self.btn.setIconSize(sz) + + def setInfoDic(self, infodic): + self.infodic = infodic + + # self.btnLabel.setText(self.infodic["toolName"]) + + def mousePressEvent(self, event): + super(CreasePlusBtn, self).mousePressEvent(event) + self.pressed.emit() + + def pressedConnect(self, func): + self.btn.pressed.connect(func) + + # self.pressed.connect(func) + + def enterEvent(self, event): + super(CreasePlusBtn, self).enterEvent(event) + if self.infoBubble: + self.infoBubble.setWindowOpacity(1) + + self.infoBubble.titleico.setText(self.infodic['toolName']) + self.infoBubble.description.setText(self.infodic['toolDesc']) + + def leaveEvent(self, event): + super(CreasePlusBtn, self).leaveEvent(event) + if self.infoBubble: + self.infoBubble.setWindowOpacity(0) + + def paintEvent(self, event): + if not isinstance(event, QCloseEvent): + + super(CreasePlusBtn, self).paintEvent(event) + + painter = QPainter(self) + + option = QStyleOption() + if whatpyside() == 1: + option.initFrom(self) + else: + option.init(self) + style = self.style() + style.drawPrimitive(QStyle.PE_Widget, option, painter, self) + + +class CreasePlusSideShape(QWidget): + + pressed = Signal() + + def __init__(self, parent=None): + + super(CreasePlusSideShape, self).__init__(parent) + self.maskShape = QPixmap() + + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup + | Qt.WA_TranslucentBackground) + self.setAttribute(Qt.WA_DeleteOnClose) + self.setWindowOpacity(1) + global global_cPsideshapestyle1 + self.setStyleSheet(global_cPsideshapestyle1) + + self.mainLay = QVBoxLayout() + self.setLayout(self.mainLay) + + self.resize(32, 32) + + def setMaskShape(self, maskshape): + self.maskShape = maskshape + + def resizeEvent(self, event): + super(CreasePlusSideShape, self).resizeEvent(event) + + self.resize(self.maskShape.size()) + self.setMask(self.maskShape.mask()) + + def mousePressEvent(self, event): + super(CreasePlusSideShape, self).mousePressEvent(event) + self.pressed.emit() + +def creaseplusclosethefucknwinbefoh(clientobj = None): + global global_creasePlusMainUi + if cmds.window(global_creasePlusMainUi,ex=True): + # print("hey here!") + cmds.deleteUI(global_creasePlusMainUi, wnd=True) + + +class CreasePlusMain(QWidget): + + gripsz = cPsizeRatio(w=10) * cPscreenSize()[0] + gripszMarg = (2, 2) + + numPages = 3 + + def __init__(self, parent=None): + + super(CreasePlusMain, self).__init__(parent) + + self.noOpacity = False + self.origw = 35 + self.origh = 370 + self.hostApp = parent + self.countDown = 50 + global global_creasePlusMainUi + self.setObjectName(global_creasePlusMainUi) + + self.setStyleSheet(''' + QWidget {border: 0px solid #ffffff;background: #444444;border-radius: 0px;color: #ffffff; + }''') + + self.pageIndex = 0 + self.pages = [ + CreasePlusMainPage() for i in range(CreasePlusMain.numPages) + ] + self.pageBtns = {} + self.oldPos = QPoint() + self.resizing = False + self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_DeleteOnClose) + # register callbacks + self.mayaCallbacks = [] + cbid = om.MSceneMessage.addCallback(om.MSceneMessage.kBeforeNew, creaseplusclosethefucknwinbefoh) + self.mayaCallbacks.append(cbid) + cbid = om.MSceneMessage.addCallback(om.MSceneMessage.kBeforeOpen, creaseplusclosethefucknwinbefoh) + self.mayaCallbacks.append(cbid) + cbid = om.MSceneMessage.addCallback(om.MSceneMessage.kMayaExiting, creaseplusclosethefucknwinbefoh) + self.mayaCallbacks.append(cbid) + + # + closeAction = QAction( + 'Close', self, shortcut='Alt+F4', triggered=self.close) + self.addAction(closeAction) + + self.setWindowTitle('CreasePlus') + + self.mainLay = QGridLayout() + self.setLayout(self.mainLay) + + self.mainLay.setContentsMargins(2, 16, 2, 16) + self.mainLay.setVerticalSpacing(4) + self.menuBar = QMenuBar() + self.menuBar.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.mainLay.addWidget(self.menuBar, 0, 0) + + self.spaceitm1 = QSpacerItem(0, cPsizeRatio(h=20) * cPscreenSize()[1], QSizePolicy.Ignored, + QSizePolicy.MinimumExpanding) + self.mainLay.addItem(self.spaceitm1, 1, 0) + + self.doMenus() + + self.hoverDelay = 0 + self.infoBubble = CreasePlusInfopop(parent=self) + self.nextWid = CreasePlusSideShape(parent=self) + self.nextWid.pressed.connect(self.onClick_next) + + + nextWidSize = cPsizeRatio(w=10) * cPscreenSize()[0] + self.nextWid.setMaskShape( + QPixmap(icoStr('next')).scaled(nextWidSize, nextWidSize, Qt.IgnoreAspectRatio, + Qt.SmoothTransformation)) + + self.timer = QTimer() + self.timer.timeout.connect(self.opacityModifier) + self.timer.start(self.countDown) + + self.doFirstPage() + self.doSecondPage() + self.doThirdPage() + + self.stackedWidget = QStackedWidget() + for page in self.pages: + self.stackedWidget.addWidget(page) + self.mainLay.addWidget(self.stackedWidget, 2, 0) + + self.infoBubble.show() + self.infoBubble.setWindowOpacity(0) + self.nextWid.show() + + self.mainRecRatio = (cPsizeRatio(w=self.origw), cPsizeRatio(h=self.origh)) + w = self.mainRecRatio[0] * cPscreenSize()[0] + h = self.mainRecRatio[1] * cPscreenSize()[1] + self.setMaximumWidth(w) + self.setMinimumWidth(w) + + # print((self.maximumWidth() ) + + self.resize(w, h) + + + def doMenus(self): + + deleteCpAttrsAc = QAction( + 'Clean Attributes', self, triggered=crepcore.cPcleanAttrs) + + + cleanHBevelLiveAc = QAction( + 'Clean HBevel Live', self, triggered=crepcore.creasePlusBakeHBL) + + toggleLastAc = QAction( + 'Toggle Last', self, triggered=crepcore.creasePlusLastCtx) + transferHBevelAc = QAction( + 'Transfer HBevel', + self, + triggered=crepcore.creasePlusTransferHBevel) + invkCreaseSetAc = QAction( + 'Crease Set Editor', self, triggered=crepcore.cPshowCreaseEd) + + self.miscMenu = QMenu('Miscs', self) + self.miscMenu.setIcon(QPixmap(icoStr('menu'))) + self.miscMenu.addAction(cleanHBevelLiveAc) + self.miscMenu.addAction(toggleLastAc) + self.miscMenu.addAction(transferHBevelAc) + self.miscMenu.addAction(deleteCpAttrsAc) + self.miscMenu.addAction(invkCreaseSetAc) + + self.menuBar.addMenu(self.miscMenu) + + def opacityOverride(self, o): + if self.noOpacity == False: + self.setWindowOpacity(o) + else: + self.setWindowOpacity(1) + def opacityModifier(self): + + # curpage = self.pages[self.stackedWidget.currentIndex()] + + try: + if self.underMouse(): + self.timer.stop() + else: + + if not self.timer.isActive(): + self.timer.start() + if self.hoverDelay >= self.countDown: + self.hoverDelay = 0 + self.opacityOverride(0.1) + self.timer.stop() + else: + self.hoverDelay += 1 + opacity = 1.0 - float(self.hoverDelay) / float( + self.countDown) + self.opacityOverride(max(0.1, opacity)) + except: + + try: + self.timer.stop() + except: + pass + + def reposNextWid(self): + + # pass + + spaceitmLeft = self.spaceitm1.geometry().topLeft() + spaceitmHeight = abs( + self.spaceitm1.geometry().bottomLeft().y() - spaceitmLeft.y()) + self.nextWid.move( + self.mapToGlobal(self.rect().topRight()).x(), + self.mapToGlobal(spaceitmLeft + QPoint( + 0, spaceitmHeight / 2 - self.nextWid.height() * 0.25)).y()) + + + def doFirstPage(self): + + pageLay = self.pages[0].layout() + pageLay.setSpacing(0) + + numbtns = 12 + + listBtns = [] + + icosz = cPsizeRatio(w=20) * cPscreenSize()[0] + for i in range(numbtns): + listBtns.append(CreasePlusBtn(infobubble=self.infoBubble)) + listBtns[i].setPixmapSize(QSize(icosz, icosz)) + + i = 0 + + listBtns[i].setPixmap(QPixmap(icoStr('bool'))) + listBtns[i].setInfoDic({ + 'toolName': 'Bool Op', + 'toolDesc': 'Performs non destructive boolean.' + }) + listBtns[i].pressedConnect(partial(crepcore.creasePlusBool, False)) + i += 1 + + + listBtns[i].setPixmap(QPixmap(icoStr('panelbool'))) + listBtns[i].setInfoDic({ + 'toolName': 'Panel Bool', + 'toolDesc': 'Performs panel bool operation.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusPanelBool) + i += 1 + + + + # + + listBtns[i].setPixmap(QPixmap(icoStr('smoothangle'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Smooth 30', + 'toolDesc': + 'Fixes and Smooths normals by a 30 degree angle.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusSmooth30) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('displayhe'))) + listBtns[i].setInfoDic({ + 'toolName': 'Display HardEdges', + 'toolDesc': 'Toggles display of hard edges.' + }) + listBtns[i].pressedConnect( + partial(crepcore.creasePlusDisplayHardEdges, 0)) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('bevel'))) + listBtns[i].setInfoDic({ + 'toolName': + 'HBevel', + 'toolDesc': + 'Bevels hard edges , based on selection.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusHBevel) + i += 1 + # + + + listBtns[i].setPixmap(QPixmap(icoStr('bevellive'))) + listBtns[i].setInfoDic({ + 'toolName': + 'HBevel Live', + 'toolDesc': + 'HBevel as a node, with a cage mesh (interactive).' + }) + listBtns[i].pressedConnect(crepcore.creasePlusHBevelLive) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('shapeshifter'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Shape Shifter', + 'toolDesc': + 'Calls ShapeShifter script (3rd party).' + }) + listBtns[i].pressedConnect(crepcore.creasePlusShapeShifter) + i += 1 + + + + + listBtns[i].setPixmap(QPixmap(icoStr('meshslicer'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Mesh Slicer', + 'toolDesc': + 'Slices the mesh in x,y,z direction based on camera , with a curve.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusCurveSlice) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('hardedge'))) + listBtns[i].setInfoDic({ + 'toolName': 'Sel HardEdges', + 'toolDesc': 'Selects hard edges.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusSelHardEdges) + i += 1 + + + + listBtns[i].setPixmap(QPixmap(icoStr('mirror'))) + listBtns[i].setInfoDic({ + 'toolName': 'Mirror', + 'toolDesc': 'Mirrors selected meshes.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusMirror) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('uv'))) + listBtns[i].setInfoDic({ + 'toolName': 'Make UV', + 'toolDesc': "Makes UV's based on hard edges." + }) + listBtns[i].pressedConnect(crepcore.creasePlusMakeUv) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('zbrush'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Goz', + 'toolDesc': + 'Exports selection in Zbrush without ngons.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusGoz) + i += 1 + + for btn in listBtns: + pageLay.addWidget(btn) + + self.pageBtns['page1'] = listBtns + + def doSecondPage(self): + pageLay = self.pages[1].layout() + pageLay.setSpacing(0) + + numbtns = 8 + + listBtns = [] + + icosz = cPsizeRatio(w=20) * cPscreenSize()[0] + for i in range(numbtns): + listBtns.append(CreasePlusBtn(infobubble=self.infoBubble)) + listBtns[i].setPixmapSize(QSize(icosz, icosz)) + + i = 0 + listBtns[i].setPixmap(QPixmap(icoStr('crease'))) + listBtns[i].setInfoDic({ + 'toolName': 'Crease1', + 'toolDesc': 'Applies first creasing preset.' + }) + listBtns[i].pressedConnect(partial(crepcore.creasePlusCreasePreset, 1)) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('crease'))) + listBtns[i].setInfoDic({ + 'toolName': 'Crease2', + 'toolDesc': 'Applies second creasing preset.' + }) + listBtns[i].pressedConnect(partial(crepcore.creasePlusCreasePreset, 2)) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('crease'))) + listBtns[i].setInfoDic({ + 'toolName': 'Crease3', + 'toolDesc': 'Applies third creasing preset.' + }) + listBtns[i].pressedConnect(partial(crepcore.creasePlusCreasePreset, 3)) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('weighttool'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Creasing Tool', + 'toolDesc': + 'Invokes the interactive creasing tool.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusWeigthTool) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('displayhe'))) + listBtns[i].setInfoDic({ + 'toolName': 'NoCrease', + 'toolDesc': 'Remove creasing on selection.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusNocrease) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('hardedge'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Smooth SG', + 'toolDesc': + 'Subdivides based on smoothing groups (retains hard edges shape).' + }) + listBtns[i].pressedConnect(crepcore.creasePlusSmoothGroupsSubD) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('physcrease'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Physical Crease', + 'toolDesc': + 'Adds edge loops around selection or hard edges.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusPhysicalCrease) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('subd'))) + listBtns[i].setInfoDic({ + 'toolName': 'Smooth', + 'toolDesc': 'Invokes subdivision smooth preset.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusSubDpreset) + i += 1 + + for btn in listBtns: + pageLay.addWidget(btn) + + self.pageBtns['page2'] = listBtns + + def doThirdPage(self): + pageLay = self.pages[2].layout() + pageLay.setSpacing(0) + + numbtns = 7 + + listBtns = [] + + icosz = cPsizeRatio(w=20) * cPscreenSize()[0] + for i in range(numbtns): + listBtns.append(CreasePlusBtn(infobubble=self.infoBubble)) + listBtns[i].setPixmapSize(QSize(icosz, icosz)) + + i = 0 + listBtns[i].setPixmap(QPixmap(icoStr('curvedraw'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Draw Curve', + 'toolDesc': + 'Enter linear curve drawing context.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusDrawCurve) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('curvepoly'))) + listBtns[i].setInfoDic({ + 'toolName': 'Curve To Polygon', + 'toolDesc': 'Makes polygon out of curve.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusCurveToPolyCmd) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('curveclose'))) + listBtns[i].setInfoDic({ + 'toolName': 'Close Curve', + 'toolDesc': 'Closes Curve(s).' + }) + listBtns[i].pressedConnect(crepcore.creasePlusCloseCurve) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('curvebevel'))) + listBtns[i].setInfoDic({ + 'toolName': 'Bevel Curve', + 'toolDesc': 'Bevels Curve Cv.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusCurveBevelCmd) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('curveint'))) + listBtns[i].setInfoDic({ + 'toolName': 'Curve Cuts', + 'toolDesc': 'Cuts curve with selected curves.' + }) + listBtns[i].pressedConnect(crepcore.creasePlusCurveIntersect) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('curveattach'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Curve Attach', + 'toolDesc': + "Attaches two curves at joining/close CV's." + }) + listBtns[i].pressedConnect(crepcore.creasePlusAttachCurve) + i += 1 + + listBtns[i].setPixmap(QPixmap(icoStr('curvemult'))) + listBtns[i].setInfoDic({ + 'toolName': + 'Curve Multiply', + 'toolDesc': + "Rebuilds to double the number of CV's" + }) + listBtns[i].pressedConnect(crepcore.creasePlusCurveDoubleCvs) + i += 1 + + for btn in listBtns: + pageLay.addWidget(btn) + + self.pageBtns['page3'] = listBtns + + + def onClick_leftSqr(self): + self.setWindowOpacity(1) + self.noOpacity = not self.noOpacity + + def onClick_eye(self): + # print("eye clicked") + # cmds.select("pCube*", r=True) + crepcore.creasePlusToggleBoolGhost() + + def onClick_next(self): + # print("next clicked") + + self.pageIndex = (self.pageIndex + 1) % CreasePlusMain.numPages + + global global_cPsideshapestyle1 + global global_cPsideshapestyle2 + global global_cPsideshapestyle3 + + if self.pageIndex == 0 : + self.nextWid.setStyleSheet(global_cPsideshapestyle1) + elif self.pageIndex == 1 : + self.nextWid.setStyleSheet(global_cPsideshapestyle2) + elif self.pageIndex == 2 : + self.nextWid.setStyleSheet(global_cPsideshapestyle3) + + self.stackedWidget.setCurrentIndex(self.pageIndex) + + def mouseInEye(self, mousePos): + + icosz = cPsizeRatio(w=17) * cPscreenSize()[0] + spaceitmLeft = self.spaceitm1.geometry().topLeft() + spaceitmHeight = abs( + self.spaceitm1.geometry().bottomLeft().y() - spaceitmLeft.y()) + minx = self.width() / 2 - icosz / 2 + miny = spaceitmLeft.y() + spaceitmHeight / 2 - icosz / 2 + return mousePos.x() > minx and mousePos.x( + ) < minx + icosz and mousePos.y() > miny and mousePos.y( + ) < miny + icosz * 0.75 + + + + def mouseInLeftSqr(self, mousePos): + return mousePos.x() < CreasePlusMain.gripsz + CreasePlusMain.gripszMarg[0] and mousePos.y( + ) > self.height() - CreasePlusMain.gripsz - CreasePlusMain.gripszMarg[1] + + def mouseInGrip(self, mousePos): + return mousePos.x() > self.width() - CreasePlusMain.gripsz - CreasePlusMain.gripszMarg[0] and mousePos.y( + ) > self.height() - CreasePlusMain.gripsz - CreasePlusMain.gripszMarg[1] + + def mouseInCloseSqr(self, mousePos): + return mousePos.x() > self.width() - CreasePlusMain.gripsz - CreasePlusMain.gripszMarg[0] and mousePos.y( + ) < CreasePlusMain.gripsz + CreasePlusMain.gripszMarg[1] + + def mousePressEvent(self, event): + super(CreasePlusMain, self).mousePressEvent(event) + if event.button() == Qt.LeftButton: + self.resizing = False + self.dragPosition = event.globalPos() - self.frameGeometry().topLeft() + if self.mouseInEye(event.pos()): + self.onClick_eye() + elif self.mouseInGrip(event.pos()): + self.oldPos = event.pos() + self.resizing = True + elif self.mouseInLeftSqr(event.pos()): + self.onClick_leftSqr() + elif self.mouseInCloseSqr(event.pos()): + event.accept() + self.close() + event.accept() + + def enterEvent(self, event): + super(CreasePlusMain, self).enterEvent(event) + + + self.hoverDelay = 0 + + # curpage = self.pages[self.stackedWidget.currentIndex()] + + self.setWindowOpacity(1) + + # if self.mouseInCloseSqr(QCursor.pos()): + # self.infoBubble.titleico.setText('dummy') + # self.infoBubble.description.setText('dummydesc') + # self.infoBubble.setWindowOpacity(1) + + # print("enter") + + def leaveEvent(self, event): + super(CreasePlusMain, self).leaveEvent(event) + self.infoBubble.setWindowOpacity(0) + + if self.timer: + if not self.timer.isActive(): + + # print("leave, restart timer...") + + self.timer.start(self.countDown) + + def mouseMoveEvent(self, event): + super(CreasePlusMain, self).mouseMoveEvent(event) + if event.buttons() == Qt.LeftButton: + if self.resizing: + delta = event.pos() - self.oldPos + self.oldPos = event.pos() + self.setMaximumWidth(16777215) + self.resize(self.width() + delta.x(), + self.height() + delta.y()) + event.accept() + self.updateGeometry() + else: + + self.move(event.globalPos() - self.dragPosition) + self.reposNextWid() + event.accept() + + def resizeEvent(self, event): + super(CreasePlusMain, self).resizeEvent(event) + + radius = 2.0 + painterpath = QPainterPath() + painterpath.addRoundedRect(QRectF(self.rect()), radius, radius) + maskedRegion = QRegion(painterpath.toFillPolygon().toPolygon()) + self.setMask(maskedRegion) + + def paintEvent(self, event): + + super(CreasePlusMain, self).paintEvent(event) + + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.setOpacity(0.3) + + + painter.drawRoundedRect( + self.width() - CreasePlusMain.gripsz - + CreasePlusMain.gripszMarg[0], + self.height() - CreasePlusMain.gripsz - + CreasePlusMain.gripszMarg[1], + CreasePlusMain.gripsz, + CreasePlusMain.gripsz, + 1, + 1) + + painter.setPen(Qt.NoPen) + painter.setOpacity(1) + painter.setBrush(QColor(32+7,32+7,32+7)) + + painter.drawRoundedRect( + CreasePlusMain.gripszMarg[0], + self.height() - CreasePlusMain.gripsz - CreasePlusMain.gripszMarg[1], + CreasePlusMain.gripsz, + CreasePlusMain.gripsz, 3,3) + + + painter.setOpacity(1) + painter.setPen(Qt.NoPen) + + # nextico = QPixmap(icoStr("next")) + # icosz = CreasePlusMain.gripsz + # painter.drawPixmap(CreasePlusMain.gripszMarg[0], self.height()-icosz-CreasePlusMain.gripszMarg[1], + # nextico.scaled(icosz, icosz, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)) + + closeico = QPixmap(icoStr('close')) + icosz = CreasePlusMain.gripsz + painter.drawPixmap(self.width() - icosz - CreasePlusMain.gripszMarg[0], + CreasePlusMain.gripszMarg[1], + closeico.scaled(icosz, icosz, Qt.IgnoreAspectRatio, + Qt.SmoothTransformation)) + + eyeico = QPixmap(icoStr('eye')) + icosz = cPsizeRatio(w=17) * cPscreenSize()[0] + + try: + self.spaceitm1 + except: + start() + return None + spaceitmLeft = self.spaceitm1.geometry().topLeft() + spaceitmHeight = abs( + self.spaceitm1.geometry().bottomLeft().y() - spaceitmLeft.y()) + painter.drawPixmap(self.width() / 2 - icosz / 2, + spaceitmLeft.y() + spaceitmHeight / 2 - icosz / 2, + eyeico.scaled(icosz, icosz, Qt.IgnoreAspectRatio, + Qt.SmoothTransformation)) + + infbubx = self.mapToGlobal(self.rect().topRight()).x() + 4 + self.infoBubble.resize(0, 0) + self.infoBubble.move(infbubx, QCursor.pos().y()) + + self.reposNextWid() + + def closeEvent(self, event): + super(CreasePlusMain, self).closeEvent(event) + + # print("CLOSEEVENT") + try: + self.mayaCallbacks + except: + pass + else: + for cb in self.mayaCallbacks: + om.MMessage.removeCallback(cb) + + try: + self.timer + except: + pass + else: + self.timer.stop() + self.timer = None + # self.infoBubble.close() + # self.nextWid.close() + + def dummy(self): + print(('dummy')) + + + +def start(): + # self = CreasePlusMain(parent=cPmayaMainWindow()) + # self.show() + creaseplusclosethefucknwinbefoh() + + mywin = CreasePlusMain(parent=cPmayaMainWindow()) + mywin.show() \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlusNodes.py b/Scripts/Modeling/Edit/CreasePlus/CreasePlusNodes.py new file mode 100644 index 0000000..7cf496e --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/CreasePlusNodes.py @@ -0,0 +1,507 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +_____ _____ ______ _____ ______ +/ ____| __ \| ____| /\ / ____| ____|_ +| | | |__) | |__ / \ | (___ | |__ _| |_ +| | | _ /| __| / /\ \ \___ \| __|_ _| +| |____| | \ \| |____ / ____ \ ____) | |____|_| +\_____|_| \_\______/_/ \_\_____/|______| + +""" + +# import maya.cmds as mc +import maya.api.OpenMaya as om +# import copy + +# uses python maya 2 + +maya_useNewAPI = True + +################# + + +def cPhardEdgeIds(mesh): + edgeIter = om.MItMeshEdge(mesh) + ids = [] + while not edgeIter.isDone(): + if not edgeIter.isSmooth: + ids.append(edgeIter.index()) + edgeIter.next() + return ids + + +def cPcompToIds(compdata, cptyp): + compDataFn = om.MFnComponentListData(compdata) + # compDataFn + ids = [] + for i in range(compDataFn.length()): + curcomp = compDataFn.get(i) + if curcomp.hasFn(cptyp): + sic = om.MFnSingleIndexedComponent(curcomp) + for j in range(sic.elementCount): + curIdx = sic.element(j) + ids.append(curIdx) + return ids + + +def cPidsToComp(ids, cptyp): + sic = om.MFnSingleIndexedComponent() + sic.create(cptyp) # om.MFn.kMeshEdgeComponent + + sic.addElements(ids) + + compData = om.MFnComponentListData() + compObj = compData.create() + compData.add(sic.object()) + return (sic.object(), compObj) + + +def cPtransformMeshPoints(mesh, mat): + meshFn = om.MFnMesh(mesh) + pts = meshFn.getPoints() + for pt in pts: + pt *= mat + + meshFn.setPoints(pts) + + +""" +input: mesh +output: componentlist +""" + +MayaNodeT = om.MPxNode + + +class CpCurveBevel(MayaNodeT): + + kNodeName = "creasePlusCurveBevel" + kNodeId = om.MTypeId(0x1154) + + aCvs = None + aOffset = None + aSeg = None + aOffsetFrac = None + aInputCurve = None + aOutput = None + + def __init__(self): + super(CpCurveBevel, self).__init__() + + @classmethod + def creator(cls): + return cls() + + @classmethod + def initialize(cls): + + nAttr = om.MFnNumericAttribute() + tAttr = om.MFnTypedAttribute() + + cls.aCvs = tAttr.create("cvComponentList", "cvs", + om.MFnComponentListData.kComponentList) + + cls.aOffset = nAttr.create("offset", "off", om.MFnNumericData.kFloat) + nAttr.setMin(0) + nAttr.default = 0.5 + nAttr.keyable = True + cls.aSeg = nAttr.create("segments", "seg", om.MFnNumericData.kInt) + nAttr.setMin(1) + nAttr.default = 1 + nAttr.keyable = True + + cls.aOffsetFrac = nAttr.create("offsetAsFraction", "oaf", + om.MFnNumericData.kBoolean) + nAttr.default = False + nAttr.keyable = True + + cls.aInputCurve = tAttr.create("inCurve", "inc", + om.MFnData.kNurbsCurve) + cls.aOutput = tAttr.create("outCurve", "out", om.MFnData.kNurbsCurve) + tAttr.storable = False + tAttr.writable = False + + MayaNodeT.addAttribute(cls.aCvs) + MayaNodeT.addAttribute(cls.aOffset) + MayaNodeT.addAttribute(cls.aSeg) + MayaNodeT.addAttribute(cls.aOffsetFrac) + MayaNodeT.addAttribute(cls.aInputCurve) + MayaNodeT.addAttribute(cls.aOutput) + + MayaNodeT.attributeAffects(cls.aCvs, cls.aOutput) + MayaNodeT.attributeAffects(cls.aOffset, cls.aOutput) + MayaNodeT.attributeAffects(cls.aSeg, cls.aOutput) + MayaNodeT.attributeAffects(cls.aOffsetFrac, cls.aOutput) + MayaNodeT.attributeAffects(cls.aInputCurve, cls.aOutput) + + def setOutputToCopy(self, data): + + inCurveHandle = data.inputValue(CpCurveBevel.aInputCurve) + outHandle = data.outputValue(CpCurveBevel.aOutput) + outHandle.copy(inCurveHandle) + outHandle.setClean() + + def compute(self, plug, data): + + if plug != CpCurveBevel.aOutput: + return None + + inCurveHandle = data.inputValue(CpCurveBevel.aInputCurve) + + inCurve = inCurveHandle.asNurbsCurveTransformed() + + inCvsHandle = data.inputValue(CpCurveBevel.aCvs) + compData = inCvsHandle.data() + ids = cPcompToIds(compData, om.MFn.kCurveCVComponent) + # + inOffset = data.inputValue(CpCurveBevel.aOffset).asFloat() + inSeg = data.inputValue(CpCurveBevel.aSeg).asInt() + inFrac = data.inputValue(CpCurveBevel.aOffsetFrac).asBool() + + if len(ids) == 0 or inOffset == 0: + self.setOutputToCopy(data) + return + + curveFn = om.MFnNurbsCurve(inCurve) + curveDegree = curveFn.degree + curveForm = curveFn.form + # curveKnots = curveFn.knots() + curvePts = curveFn.cvPositions() # to be modified + numcv = len(curvePts) # to be modified + + bevelParam = 1 / float(inSeg) + bevelPts = [] + + ids.sort() + + for cvIdx in ids: + + mpos = om.MVector(curvePts[cvIdx]) + + if curveForm == curveFn.kPeriodic: + lpos = om.MVector(curvePts[(cvIdx + numcv - + (1 + curveDegree)) % + (numcv - curveDegree)]) + rpos = om.MVector(curvePts[(cvIdx + 1) % + (numcv - curveDegree)]) + else: + lpos = om.MVector(curvePts[(cvIdx + numcv - 1) % numcv]) + rpos = om.MVector(curvePts[(cvIdx + 1) % numcv]) + + lvec = lpos - mpos + rvec = rpos - mpos + + lanchor = None + ranchor = None + if inFrac: + lanchor = mpos + (inOffset * lvec) + ranchor = mpos + (inOffset * rvec) + else: + lanchor = mpos + (inOffset * lvec.normal()) + ranchor = mpos + (inOffset * rvec.normal()) + + bevelPts.append(lanchor) + t = 1 * bevelParam + for i in range(inSeg - 1): + # (1-t)^2A+2t(1-t)B+t^2C + res = (1 - t)**2 * lanchor + (2 * t * (1 - t) * mpos) + ( + t**2 * ranchor) + bevelPts.append(res) + t += bevelParam + bevelPts.append(ranchor) + + # + i = len(bevelPts) - (inSeg + 1) + ids.sort(reverse=True) + for cvIdx in ids: + curvePts[cvIdx] = bevelPts[i] + curvePts[cvIdx + 1:cvIdx + 1] = bevelPts[i + 1:i + inSeg + 1] + i -= (inSeg + 1) + + numcv = len(curvePts) + if curveForm == curveFn.kPeriodic: + curvePts[-curveDegree:] = curvePts[:curveDegree] + + if curveDegree == 1: + knots = [float(i) for i in range(numcv)] + + else: + knots = [None] * (numcv - curveDegree + (2 * curveDegree) - 1) + + if curveForm == curveFn.kPeriodic: + knots[:curveDegree] = [ + float(i) for i in reversed(range(0, -curveDegree, -1)) + ] + knots[curveDegree:] = [ + float(i) for i in range(1, + len(knots) - curveDegree + 1) + ] + + else: + knots[:curveDegree] = [float(0)] * curveDegree + knots[-curveDegree:] = [float(numcv - curveDegree) + ] * curveDegree + knots[curveDegree:-curveDegree] = [ + float(i) + for i in range(1, + len(knots) - (2 * curveDegree) + 1) + ] + + curveDataFn = om.MFnNurbsCurveData() + curveDataFn.create() + + knots = om.MDoubleArray(knots) + curveFn.create( + curvePts, + knots, + curveDegree, + curveForm, + False, + False, + parent=curveDataFn.object()) + + out = data.outputValue(CpCurveBevel.aOutput) + out.setMObject(curveDataFn.object()) + out.setClean() + + +class CpCurveToPoly(MayaNodeT): + + kNodeName = "creasePlusCurveToPoly" + kNodeId = om.MTypeId(0x12547) + + aCount = None + aRevNorm = None + aControlPts = None + aInputCurve = None + aOutput = None + + def __init__(self): + super(CpCurveToPoly, self).__init__() + + @classmethod + def creator(cls): + return cls() + + @classmethod + def initialize(cls): + + nAttr = om.MFnNumericAttribute() + tAttr = om.MFnTypedAttribute() + + cls.aRevNorm = nAttr.create("reverse", "rev", + om.MFnNumericData.kBoolean) + nAttr.default = False + nAttr.keyable = True + + cls.aCount = nAttr.create("count", "cnt", om.MFnNumericData.kInt) + nAttr.setMin(3) + nAttr.default = 12 + nAttr.keyable = True + + cls.aControlPts = nAttr.create("controlPts", "cv", + om.MFnNumericData.kBoolean) + nAttr.default = True + nAttr.keyable = True + + cls.aInputCurve = tAttr.create("inCurve", "inc", + om.MFnData.kNurbsCurve) + cls.aOutput = tAttr.create("outPoly", "out", om.MFnData.kMesh) + tAttr.storable = False + tAttr.writable = False + + MayaNodeT.addAttribute(cls.aRevNorm) + MayaNodeT.addAttribute(cls.aCount) + MayaNodeT.addAttribute(cls.aControlPts) + MayaNodeT.addAttribute(cls.aInputCurve) + MayaNodeT.addAttribute(cls.aOutput) + + MayaNodeT.attributeAffects(cls.aRevNorm, cls.aOutput) + MayaNodeT.attributeAffects(cls.aCount, cls.aOutput) + MayaNodeT.attributeAffects(cls.aControlPts, cls.aOutput) + MayaNodeT.attributeAffects(cls.aInputCurve, cls.aOutput) + + def setOutputToNull(self, data): + out = data.outputValue(CpCurveToPoly.aOutput) + out.setMObject(om.MObject.kNullObj) + out.setClean() + + def compute(self, plug, data): + + if plug != CpCurveToPoly.aOutput: + return None + + reverseNormal = data.inputValue(CpCurveToPoly.aRevNorm).asBool() + inCurveHandle = data.inputValue(CpCurveToPoly.aInputCurve) + + inCurve = inCurveHandle.asNurbsCurveTransformed() + + inCount = data.inputValue(CpCurveToPoly.aCount).asInt() # + useCvs = data.inputValue(CpCurveToPoly.aControlPts).asBool() + + curveFn = om.MFnNurbsCurve(inCurve) + curveForm = curveFn.form + curveDegree = curveFn.degree + polyPts = None + + meshDataFn = om.MFnMeshData() + meshDataFn.create() + + if useCvs: + if curveForm == curveFn.kPeriodic: + polyPts = curveFn.cvPositions()[:-curveDegree] + else: + polyPts = curveFn.cvPositions() + else: + polyPts = [] + domain = curveFn.knotDomain + param = domain[1] / float(inCount) + t = 0.0 + for i in range(inCount): + polyPts.append(curveFn.getPointAtParam(t)) + t += param + + if reverseNormal: + polyPts = [pt for pt in reversed(polyPts)] + # create(vertices, polygonCounts, polygonConnects, uValues=None, vValues=None, parent=kNullObj) -> MObject + meshFn = om.MFnMesh() + meshFn.create( + polyPts, [len(polyPts)], [i for i in range(len(polyPts))], + parent=meshDataFn.object()) + + out = data.outputValue(CpCurveToPoly.aOutput) + out.setMObject(meshDataFn.object()) + out.setClean() + + +class CpHeIds(MayaNodeT): + + kNodeName = "creasePlusBevelHe" + kNodeId = om.MTypeId(0x1157) + + aForceComp = None + aInputMesh = None + aIds = None + + def __init__(self): + + super(CpHeIds, self).__init__() + + self.numVertices = 0 + self.numPolygons = 0 + self.numNormals = 0 + self.dummycompute = False + + @classmethod + def creator(cls): + return cls() + + @classmethod + def initialize(cls): + tAttr = om.MFnTypedAttribute() + nAttr = om.MFnNumericAttribute() + + cls.aForceComp = nAttr.create("forceCompute", "fc", + om.MFnNumericData.kBoolean, 0) + nAttr.default = False + nAttr.keyable = True + + cls.aInputMesh = tAttr.create("inMesh", "i", om.MFnData.kMesh) + + cls.aIds = tAttr.create("componentList", "cl", + om.MFnComponentListData.kComponentList) + tAttr.storable = False + tAttr.writable = False + + MayaNodeT.addAttribute(cls.aForceComp) + MayaNodeT.addAttribute(cls.aInputMesh) + MayaNodeT.addAttribute(cls.aIds) + + def attrToPlug(self, attr): + return om.MPlug(self.thisMObject(), attr) + + def setDependentsDirty(self, plug, affect): + + if plug == CpHeIds.aForceComp: + if self.dummycompute == False: + self.dummycompute = True + affect.append(self.attrToPlug(CpHeIds.aIds)) + else: + self.dummycompute = False + elif plug == CpHeIds.aInputMesh: + affect.append(self.attrToPlug(CpHeIds.aIds)) + + def compute(self, plug, data): + if plug != CpHeIds.aIds: + return None + + doingit = False + + data.inputValue(CpHeIds.aForceComp) + inMeshHandle = data.inputValue(CpHeIds.aInputMesh) + inmesh = inMeshHandle.asMesh() + meshFn = om.MFnMesh(inmesh) + + if self.dummycompute == True: + doingit = True + elif (self.numVertices != meshFn.numVertices + or self.numPolygons != meshFn.numPolygons + or self.numNormals != meshFn.numNormals): + doingit = True + + if doingit == True: + heIds = cPhardEdgeIds(inmesh) + compObj = cPidsToComp(heIds, om.MFn.kMeshEdgeComponent)[1] + + outIdsHandle = data.outputValue(CpHeIds.aIds) + outIdsHandle.setMObject(compObj) + outIdsHandle.setClean() + + if self.dummycompute == True: + forceCompplug = self.attrToPlug(CpHeIds.aForceComp) + forceCompplug.setBool(False) + + +def initializePlugin(obj): + + mplugin = om.MFnPlugin(obj, "Baidhir Hidair", "1.0") + + nodeName = None + + try: + nodeName = CpHeIds.kNodeName + mplugin.registerNode(CpHeIds.kNodeName, CpHeIds.kNodeId, + CpHeIds.creator, CpHeIds.initialize) + + nodeName = CpCurveBevel.kNodeName + mplugin.registerNode(CpCurveBevel.kNodeName, CpCurveBevel.kNodeId, + CpCurveBevel.creator, CpCurveBevel.initialize) + + nodeName = CpCurveToPoly.kNodeName + mplugin.registerNode(CpCurveToPoly.kNodeName, CpCurveToPoly.kNodeId, + CpCurveToPoly.creator, CpCurveToPoly.initialize) + + except: + raise Exception('failed to register node: ' + nodeName) + + +def uninitializePlugin(obj): + + mplugin = om.MFnPlugin(obj) + + nodeName = None + + try: + nodeName = CpHeIds.kNodeName + mplugin.deregisterNode(CpHeIds.kNodeId) + + nodeName = CpCurveBevel.kNodeName + mplugin.deregisterNode(CpCurveBevel.kNodeId) + + nodeName = CpCurveToPoly.kNodeName + mplugin.deregisterNode(CpCurveToPoly.kNodeId) + + except: + raise Exception('failed to deregister node: ' + nodeName) \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/CreasePlus_def_sheet.pdf b/Scripts/Modeling/Edit/CreasePlus/CreasePlus_def_sheet.pdf new file mode 100644 index 0000000..cf6315c Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/CreasePlus_def_sheet.pdf differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bevel_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bevel_ico.png new file mode 100644 index 0000000..94b68c7 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bevel_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bevellive_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bevellive_ico.png new file mode 100644 index 0000000..f429833 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bevellive_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bool_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bool_ico.png new file mode 100644 index 0000000..e778f25 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_bool_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_close_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_close_ico.png new file mode 100644 index 0000000..f1545f8 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_close_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_crease_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_crease_ico.png new file mode 100644 index 0000000..304dfed Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_crease_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveattach_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveattach_ico.png new file mode 100644 index 0000000..ccdf57d Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveattach_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvebevel_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvebevel_ico.png new file mode 100644 index 0000000..b07b240 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvebevel_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveclose_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveclose_ico.png new file mode 100644 index 0000000..ca6b447 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveclose_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvedraw_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvedraw_ico.png new file mode 100644 index 0000000..e34fbea Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvedraw_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveint_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveint_ico.png new file mode 100644 index 0000000..ac13f1e Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curveint_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvemult_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvemult_ico.png new file mode 100644 index 0000000..cb59056 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvemult_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvepoly_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvepoly_ico.png new file mode 100644 index 0000000..37051b6 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_curvepoly_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_displayhe_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_displayhe_ico.png new file mode 100644 index 0000000..213c70b Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_displayhe_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_eye_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_eye_ico.png new file mode 100644 index 0000000..46d482a Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_eye_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_hardedge_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_hardedge_ico.png new file mode 100644 index 0000000..c8cbcd8 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_hardedge_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_menu_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_menu_ico.png new file mode 100644 index 0000000..c89eeb1 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_menu_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_meshslicer_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_meshslicer_ico.png new file mode 100644 index 0000000..776e100 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_meshslicer_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_mirror_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_mirror_ico.png new file mode 100644 index 0000000..4051db1 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_mirror_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_next_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_next_ico.png new file mode 100644 index 0000000..86674ef Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_next_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_panelbool_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_panelbool_ico.png new file mode 100644 index 0000000..f509c57 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_panelbool_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_physcrease_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_physcrease_ico.png new file mode 100644 index 0000000..39a7551 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_physcrease_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_shapeshifter_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_shapeshifter_ico.png new file mode 100644 index 0000000..38bcbd9 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_shapeshifter_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_smoothangle_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_smoothangle_ico.png new file mode 100644 index 0000000..d018bf5 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_smoothangle_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_subd_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_subd_ico.png new file mode 100644 index 0000000..c8a1d6b Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_subd_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_uv_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_uv_ico.png new file mode 100644 index 0000000..36d89a3 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_uv_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_weighttool_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_weighttool_ico.png new file mode 100644 index 0000000..4451d3e Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_weighttool_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/Icons/crep_zbrush_ico.png b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_zbrush_ico.png new file mode 100644 index 0000000..3414e19 Binary files /dev/null and b/Scripts/Modeling/Edit/CreasePlus/Icons/crep_zbrush_ico.png differ diff --git a/Scripts/Modeling/Edit/CreasePlus/MayaUndoRun.py b/Scripts/Modeling/Edit/CreasePlus/MayaUndoRun.py new file mode 100644 index 0000000..68b4ceb --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/MayaUndoRun.py @@ -0,0 +1,17 @@ +from functools import wraps +import maya.cmds as cmds + +def mayaUndoRun(func): + """ Puts the wrapped `func` into a single Maya Undo action, then + undoes it when the function enters the finally: block """ + @wraps(func) + def _undofunc(*args, **kwargs): + try: + # start an undo chunk + cmds.undoInfo(openChunk=True) + return func(*args, **kwargs) + finally: + # after calling the func, end the undo chunk + cmds.undoInfo(closeChunk=True) + + return _undofunc \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/__init__.py b/Scripts/Modeling/Edit/CreasePlus/__init__.py new file mode 100644 index 0000000..64f3743 --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Empty Init file +from . import * \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/delpyc.bat b/Scripts/Modeling/Edit/CreasePlus/delpyc.bat new file mode 100644 index 0000000..7c2b498 --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/delpyc.bat @@ -0,0 +1,2 @@ +@echo off +del *.pyc \ No newline at end of file diff --git a/Scripts/Modeling/Edit/CreasePlus/readit.txt b/Scripts/Modeling/Edit/CreasePlus/readit.txt new file mode 100644 index 0000000..96a428c --- /dev/null +++ b/Scripts/Modeling/Edit/CreasePlus/readit.txt @@ -0,0 +1,40 @@ +_____ _____ ______ _____ ______ +/ ____| __ \| ____| /\ / ____| ____|_ +| | | |__) | |__ / \ | (___ | |__ _| |_ +| | | _ /| __| / /\ \ \___ \| __|_ _| +| |____| | \ \| |____ / ____ \ ____) | |____|_| +\_____|_| \_\______/_/ \_\_____/|______| + +This version of CreasePlus is rewritten entirely in python cmds(maya) and API's. +thus there is no longer support for MayaLT. And the run-in lang is Python. + +install : +extract, then just place icon and main folders in documents/maya/(maya_version)/scripts/ +restart maya if opened. + +# +# attach it as python script / runtime command to hotkey: +import maya.cmds as cmds +from CreasePlus import CreasePlusMain +CreasePlusMain.start() + +if not cmds.pluginInfo("CreasePlusNodes", q=True, loaded=True): + cmds.loadPlugin("CreasePlusNodes.py") + + +# attach it as python script / runtime command to hotkey, for attribute iteration in context (optional): +from CreasePlus import CreasePlusMain +CreasePlusMain.crepcore.creasePlusLastCtx() + +# attach it as python script / runtime command to hotkey, for edge soft/hard toggle (optional): +from CreasePlus import CreasePlusMain +CreasePlusMain.crepcore.creasePlusToggleEdgeSmooth() + +# attach it as python script / runtime command to hotkey, for edge makeUV (optional): +from CreasePlus import CreasePlusMain +CreasePlusMain.crepcore.creasePlusMakeUv() + + +# commands / defs for bindings / scripts can be found in def_sheet file + +# thank you \ No newline at end of file diff --git a/Scripts/Modeling/Edit/gs_curvetools/LICENSE.txt b/Scripts/Modeling/Edit/gs_curvetools/LICENSE.txt new file mode 100644 index 0000000..253c22f --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/LICENSE.txt @@ -0,0 +1,201 @@ +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +https://www.artstation.com/marketplace-product-eula + +Marketplace Product & Services Agreement +End User Agreement +This Marketplace End User Agreement applies to all downloadable products and professional services (e.g. mentorships, personal training, portfolio reviews) sold via the ArtStation Marketplace, unless a custom agreement or license is provided by the seller. + +The EUA is an agreement between the buyer and the seller providing the goods or services. + +PLEASE READ THIS DOCUMENT CAREFULLY. IT SIGNIFICANTLY ALTERS YOUR LEGAL RIGHTS AND REMEDIES. + +BY CLICKING “I AGREE” OR DOWNLOADING OR USING THE DIGITAL PRODUCT OR RECEIVING THE PROFESSIONAL SERVICES TO WHICH THIS AGREEMENT RELATES YOU ACCEPT ALL OF THIS AGREEMENT’S TERMS, INCLUDING THE DISCLAIMERS OF WARRANTIES AND LIMITATIONS ON DAMAGES, USE AND TRANSFERABILITY. IF YOU DO NOT ACCEPT THIS AGREEMENT’S TERMS, DO NOT DOWNLOAD, INSTALL OR USE THE DIGITAL PRODUCT OR RECEIVE OR USE THE PROFESSIONAL SERVICES. + +This end-user agreement (“Agreement”) is a legally binding agreement between you, the licensee and customer (“you” or “your”), and the provider (“we” or “us” or “our”) of the digital products (“Products”) or instructional, training, mentorship or other professional service packages (“Professional Services”) that you purchase through the ArtStation Marketplace, regarding your rights and obligations regarding those Products and Professional Services. + +1. Your Status +In this Agreement, “you” means the person or entity acquiring rights in the Products or purchasing Professional Services. That may be a natural person, or a corporate or business entity or organization. + +(a) If you are a natural person then you must be, and you confirm that you are, at least 13 years old. If you are between 13 years and the age of majority in your jurisdiction of residence, you confirm that your parent or legal guardian has reviewed and agrees to this Agreement and is happy for you to access and use the Product or receive the Professional Services. + +(b) If you are a corporate entity then: (i) the rights granted under this Agreement are granted to that entity; (ii) you represent and warrant that the individual completing and accepting this Agreement is an authorized your representative and has the authority to legally bind that you to the Agreement; and (iii) to the extent that one or more of your employees are granted any rights in the Product or to receive Professional Services under this Agreement, you will ensure that your employees comply with this Agreement and you will be responsible and liable for any breach of this Agreement by any employee. + +2. ArtStation +ArtStation is a division of Epic Games, Inc., You acknowledge and agree that Epic is a third-party beneficiary of this Agreement and therefore will be entitled to directly enforce and rely upon any provision in this Agreement that confers a benefit on, or rights in favour of, Epic. In addition, you authorize Epic to act as your authorized representative to file a lawsuit or other formal action against a licensor in a court or with any other governmental authority if Epic knows or suspects that a licensor breached any representations or warranties under this Agreement. The foregoing authorization is nonexclusive, and Epic shall be under no obligation to pursue any claim. Epic will not initiate any such action on your behalf without first consulting with and obtaining your approval. + +Products +The following sections 3 through 9 apply to any Products you acquire from us through the ArtStation Marketplace: + +3. Product Licence +Subject to this Agreement’s terms and conditions, we hereby grant you a limited, non-exclusive, worldwide, non-transferable right and licence to (which will be perpetual unless the licence terminates as set out in this Agreement): (a) download the Product; and (b) copy and use the Product. We reserve all rights not expressly granted to you under this Agreement. + +4. Licence Scope and Restrictions +(a) Tutorials +You are purchasing ONE licence to create ONE copy of the Product for use by you only (or, if you are a corporate entity, for use by a single authorized employee). + +If this Product is bundled with a stock digital asset then you receive a limited personal use licence regarding that stock digital asset, and you may use that stock digital asset for your personal use only. You will not use that stock digital asset in any commercial manner unless you purchase a separate commercial licence. + +(b) Installable Tools +You may purchase one or more licences for the Product. A single licence allows you to install the Product on a single computer at a time for use by a single authorized user. If you are a corporate entity and the authorized employee completing the transaction on your behalf purchases multiple licences, you may choose to store the Product on a single server or shared hard drive for use by a single authorized employee at a time for each licence purchased. + +Provided that you comply with the restrictions on users set out above, you may use the Product on an unlimited number of projects. + +(c) Stock Assets +Subject to the restrictions set out in this Agreement, you may copy, use, modify, adapt, translate, distribute, publicly display, transmit, broadcast, and create derivative works from the Product in works you create (“Works”), which may include things like films, videos, multi-media projects, computer games, models, images, publications, broadcasts, documents, and presentations. + +If you are a corporate entity, you may make the Product available for use by your employees in accordance with this Agreement (for example, by storing the Product on a network server). + +You may only share the Product with external people or entities where: + +You are collaborating with the external parties in the creation of your Work and you need to share the Product for that purpose, provided that any external party that receives the Product may only use it in your Work and must secure and limit access to the Product for that purpose; +You are working as a contractor for a client in the creation of a Work and need to share the Product with your client, or any external parties working with your client, provided that your client and any such external parties may use the Product only for your client’s Work, and all parties secure and limit access to the Product for that purpose. +For any other use of the Product by any other party, that party must purchase a licence to the Product. + +In addition to any other restrictions in this Agreement, you will not: + +publish, sell, license, offer or make available for sale or licensing, or otherwise distribute the Product except as part of a Work or through a form of sharing that is authorized in this Agreement; or +publish, distribute or make available the Product through any online clearinghouse platform. +FURTHER SPECIFIC TERMS +In addition to the restrictions set out above, the following terms and conditions apply to the following forms of commercial licences for the Product: + +Standard Commercial Licence +If you have purchased a Standard Commercial licence then you may exercise your rights under that licence: + +for personal use on an unlimited number of personal projects that are not used or distributed in any commercial manner; and +respect to one commercial Work, with up to a maximum of, as applicable, 2,000 sales of the Work or 20,000 monthly views of the Work. +Extended Commercial Licence +If you have purchased an Extended Commercial licence then you may exercise your rights under that licence: + +for personal use on an unlimited number of personal projects that are not used or distributed in any commercial manner; and +with respect to any number of commercial Works, with no limit on sales or views. +5. Additional Restrictions +Except as expressly permitted under this Agreement, you will not: + +(a) make any copy of the Product except for archival or backup purposes; + +(b) circumvent or disable any access control technology, security device, procedure, protocol, or technological protection mechanism that may be included or established in or as part of the Product; + +(c) hack, reverse engineer, decompile, disassemble, modify or create derivative works of the Product or any part of the Product; + +(d) publish, sell distribute or otherwise make the Product available to others to use, download or copy; + +(e) transfer or sub-license the Product or any rights under this Agreement to any third party, whether voluntarily or by operation of law; + +(f) use the Product for any purpose that may be defamatory, threatening, abusive, harmful or invasive of anyone’s privacy, or that may otherwise violate any law or give rise to civil or other liability; + +(g) misrepresent yourself as the creator or owner of the Property; + +(h) remove or modify any proprietary notice, symbol or label in or on the Product; + +(i) directly or indirectly assist, facilitate or encourage any third party to carry on any activity prohibited by this Agreement. + +6. Proprietary Rights +The Product is protected by copyright laws and international copyright treaties, as well as other intellectual property laws and treaties. You are licensing the Product and the right to access, install and use the Product in accordance with this Agreement, not buying the Product. As between you and us, we own all right, title and interest in and to the Product, and you are not acquiring any ownership of or rights in the Product except the limited rights granted under this Agreement. + +7. No Epic Support +You acknowledge and agree that you are licensing the Product from us (the Provider), not from Epic, and that Epic has no obligation to support the Product. + +8. Interruptions and Errors +Your use of the Product might be interrupted and might not be free of errors. + +9. Updates +We have no obligation to update the Product. + +Professional Services +The following sections 10 and 11 apply to any Professional Services you purchase from us through the ArtStation Marketplace: + +10. Provision of Professional Services +We will provide the Professional Services directly to you and, subject to this Agreement, will assume all responsibility for all aspects of the Professional Services. We represent and warrant that we have the right to offer and provide the Professional Services and that we have appropriate qualifications and experience to provide the Professional Services. + +11. Epic is not Involved +You acknowledge and agree that: + +(a) Epic is only a provider of the online ArtStation Marketplace where you purchased the Professional Services, and does not provide or exercise any control or oversight over us or the Professional Services, and is not responsible for us or the Professional Services or any shortcomings in them, including any damages, losses or legal issues caused by us or the Professional Services; + +(b) this Agreement (and any dispute under it) is an agreement between us and you only, and not with Epic, and Epic is not a party to this Agreement; + +(c) we are not Epic’s employee, agent or subcontractor; + +(d) Epic does not have any obligation to attempt to resolve any dispute between us and you; and + +(e) we will provide the Professional Services directly to you, and we (and not Epic) are solely responsible for the Professional Services, and Epic has no obligation or liability to you with respect to the Professional Services. + +Both Products and Services +The following sections 12 through 25 apply to all Products or Services you purchase from us through the ArtStation Marketplace: + +12. Disclaimer +ANY PRODUCTS OR PROFESSIONAL SERVICES ARE PROVIDED ON AN “AS IS” AND “AS AVAILABLE” BASIS, WITHOUT ANY REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY KIND. + +TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW WE DISCLAIM, AND YOU WAIVE (WITH REGARD TO US AND ALSO TO EPIC, ITS AFFILIATES, AND ITS AND THEIR LICENSORS AND SERVICE PROVIDERS (COLLECTIVELY, THE “EPIC PARTIES”), ALL TERMS, CONDITIONS, GUARANTEES, REPRESENTATIONS AND WARRANTIES (EXPRESS, IMPLIED, STATUTORY AND OTHERWISE), IN RESPECT OF THE PRODUCTS AND PROFESSIONAL SERVICES, INCLUDING THOSE OF MERCHANTABILITY, NON-INFRINGEMENT, TITLE, QUALITY AND FITNESS FOR A PARTICULAR PURPOSE. + +NEITHER WE NOR ANY OF THE EPIC PARTIES REPRESENT OR WARRANT THAT: (A) ANY PRODUCT OR PROFESSIONAL SERVICE IS ACCURATE, COMPLETE, RELIABLE, CURRENT OR ERROR-FREE; (B) ANY PRODUCT OR PROFESSIONAL SERVICE WILL MEET YOUR REQUIREMENTS OR EXPECTATIONS; (C) ANY PRODUCT OR PROFESSIONAL SERVICES IS FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS; OR (D) ANY DEFECTS IN ANY PRODUCT OR PROFESSIONAL SERVICE WILL BE CORRECTED. + +13. Exclusion and Limitation of Liability +(a) YOU DOWNLOAD, INSTALL AND OTHERWISE USE ALL PRODUCTS, AND RECEIVE AND USE ALL PROFESSIONAL SERVICES, AT YOUR OWN RISK. YOU AGREE TO, AND HEREBY DO: + +(i) WAIVE ANY CLAIMS THAT YOU MAY HAVE AGAINST US OR THE EPIC PARTIES OR OUR RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, REPRESENTATIVES, LICENSORS, SUCCESSORS AND ASSIGNS (COLLECTIVELY THE “RELEASEES”) ARISING FROM OR RELATING TO ANY PRODUCTS OR PROFESSIONAL SERVICES, AND + +(ii) RELEASE THE RELEASEES FROM ANY LIABILITY FOR ANY LOSS, DAMAGE, EXPENSE OR INJURY ARISING FROM OR RELATING TO YOUR USE OF ANY PRODUCT OR PROFESSIONAL SERVICE, WHETHER ARISING IN TORT (INCLUDING NEGLIGENCE), CONTRACT OR OTHERWISE, EVEN IF THE RELEASEES ARE EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH LOSS, INJURY OR DAMAGE AND EVEN IF THAT LOSS, INJURY OR DAMAGE IS FORESEEABLE. + +(b) NEITHER WE NOR THE EPIC PARTIES WILL BE LIABLE FOR ANY LOSSES, DAMAGES, CLAIMS OR EXPENSES THAT CONSTITUTE: (I) LOSS OF INTEREST, PROFIT, BUSINESS, CUSTOMERS OR REVENUE; (II) BUSINESS INTERRUPTIONS; (III) COST OF REPLACEMENT PRODUCTS OR SERVICES; OR (IV) LOSS OF OR DAMAGE TO REPUTATION OR GOODWILL. + +(c) NEITHER WE NOR THE EPIC PARTIES WILL BE LIABLE FOR ANY LOSSES, DAMAGES, CLAIMS OR EXPENSES THAT CONSTITUTE INCIDENTAL, CONSEQUENTIAL, SPECIAL, PUNITIVE, EXEMPLARY, MULTIPLE OR INDIRECT DAMAGES, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, DAMAGES, CLAIMS OR EXPENSES. + +(d) MAXIMUM LIABILITY: IF, DESPITE THE LIMITATIONS SET OUT ABOVE, WE OR ANY EPIC PARTY BECOME LIABLE TO YOU IN RESPECT OF ANY PRODUCT OR PROFESSIONAL SERVICE OR OTHERWISE UNDER THIS AGREEMENT, THE ENTIRE CUMULATIVE LIABILITY OF US AND THE EPIC PARTIES, AND YOUR EXCLUSIVE AND CUMULATIVE REMEDY, FOR ANY DAMAGES (REGARDLESS OF THE CAUSE OR FORM OR ACTION), WILL BE LIMITED TO CAD$10. + +14. Indemnity +As a condition of your use of any Product or any Professional Services, you agree to hold harmless and indemnify the Releasees from any liability for any loss or damage to any third party resulting from your access to, installation or use of the Product or your receipt and use of the Professional Services. + +15. Term and Termination +This Agreement is effective until terminated. Your rights under this Agreement will terminate automatically without notice if: (a) you breach any terms of this Agreement; or (b) you do not complete payment for the Product or Professional Services, or any payment you make is refunded, reversed or cancelled for any reason. Upon this Agreement’s termination, you will cease all use of the Product and destroy all copies, full or partial, of the Product in your possession. Sections 11 through 25 will survive the termination of this Agreement. + +16. Compliance with Laws +You will comply with all applicable laws when using any Product or Professional Services (including intellectual property and export control laws). + +17. Entire Agreement +This Agreement supersedes all prior agreements of the parties regarding the Product or Professional Services, and constitutes the whole agreement with respect to the Product or Professional Services. + +18. Disputes +If you have any concerns about the Product or Professional Services, please contact us through our ArtStation Marketplace account and we will work with you to try to resolve the issue. You acknowledge and agree that any such dispute is between you and us, and that Epic will not be involved in the dispute and has no obligation to try to resolve the dispute. + +19. Persons Bound +This Agreement will enure to the benefit of and be binding upon the parties and their heirs, executors, administrators, legal representatives, lawful successors and permitted assigns. + +20. Assignment +We may assign this Agreement without notice to you. You may not assign this Agreement or any of your rights under it without our prior written consent, which we will not withhold unreasonably. + +21. Waiver +No waiver, delay, or failure to act by us regarding any particular default or omission will prejudice or impair any of our rights or remedies regarding that or any subsequent default or omission that are not expressly waived in writing. + +22. Applicable Law and Jurisdiction +You agree that this Agreement will be deemed to have been made and executed in the State of North Carolina, U.S.A., and any dispute will be resolved in accordance with the laws of North Carolina, excluding that body of law related to choice of laws, and of the United States of America. Any action or proceeding brought to enforce the terms of this Agreement or to adjudicate any dispute must be brought in the Superior Court of Wake County, State of North Carolina or the United States District Court for the Eastern District of North Carolina. You agree to the exclusive jurisdiction and venue of these courts. You waive any claim of inconvenient forum and any right to a jury trial. The Convention on Contracts for the International Sale of Goods will not apply. Any law or regulation which provides that the language of a contract shall be construed against the drafter will not apply to this Agreement. + +23. Legal Effect +This Agreement describes certain legal rights. You may have other rights under the laws of your country. This Agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so. + +24. Interpretation +In this Agreement, "we", "us", and "our" refer to the licensor of the Product alone and never refer to the combination of you and that licensor (that combination is referred to as "the parties"), or the combination of you or the licensor with Epic. + +25. Artificial Intelligence +For purposes of this Agreement, “Generative AI Programs” means artificial intelligence, machine learning, deep learning, neural networks, or similar technologies designed to automate the generation of or aid in the creation of new content, including but not limited to audio, visual, or text-based content. + +We (the licensor of the Product) represent and warrant that where the Product was created using Generative AI Programs, we have applied the “CreatedWithAI” tag. Under this Agreement, a Product is considered to be created using Generative AI Programs where a material portion of a Product is generated with Generative AI Programs, whether characters, backgrounds, or other material elements. A Product is not considered to be created using Generative AI Programs merely for use of features that solely operate on a Product (e.g., AI-based upscaling or content-aware fill). diff --git a/Scripts/Modeling/Edit/gs_curvetools/README.txt b/Scripts/Modeling/Edit/gs_curvetools/README.txt new file mode 100644 index 0000000..95b782d --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/README.txt @@ -0,0 +1,35 @@ +GS CurveTools installation + +1. Copy gs_curvetools folder to {Path to Documents}\Documents\Maya\{Maya Version}\scripts\ + + Example of the final folder structure: + + Documents\Maya\2022\scripts\gs_curvetools\fonts + Documents\Maya\2022\scripts\gs_curvetools\icons + Documents\Maya\2022\scripts\gs_curvetools\utils + Documents\Maya\2022\scripts\gs_curvetools\__init__.py + Documents\Maya\2022\scripts\gs_curvetools\core.py + Documents\Maya\2022\scripts\gs_curvetools\init.py + Documents\Maya\2022\scripts\gs_curvetools\LICENSE.txt + Documents\Maya\2022\scripts\gs_curvetools\main.py + Documents\Maya\2022\scripts\gs_curvetools\README.txt + +2. Run Maya + +3. Copy and paste this line to "Python" command box and press "Enter": + +import gs_curvetools.init as ct_init;from imp import reload;reload(ct_init);ct_init.Init(); + +IMPORTANT: There should be no spaces or tabs before this command! + +4. Look for GS tab on your Shelf + +5. Click CT UI button to run the menu. Click again to hide the menu. + +NOTES: +>> To reset to factory defaults click CT with "refresh" arrow button. +>> To stop all scripts and close the menu press CT DEL button. +>> You can use middle-mouse button drag to move the buttons to any tab. +>> All the hotkeys are available in Hotkey Editor > Custom Scripts > GS > GS_CurveTools. +>> Always repeat initialization steps when updating the plug-in to a new version. +>> You can always repeat initialization steps if you lost control buttons or shelf. diff --git a/Scripts/Modeling/Edit/gs_curvetools/__init__.py b/Scripts/Modeling/Edit/gs_curvetools/__init__.py new file mode 100644 index 0000000..413c239 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/__init__.py @@ -0,0 +1,10 @@ +from . import * +from . import fonts +from . import icons +from . import plugins +from . import utils +from . import core +from . import init +from . import main +from . import ui +from . import uv_editor diff --git a/Scripts/Modeling/Edit/gs_curvetools/constants.py b/Scripts/Modeling/Edit/gs_curvetools/constants.py new file mode 100644 index 0000000..09c0b7c --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/constants.py @@ -0,0 +1,69 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" +import base64 as __B64 +from datetime import datetime as __DT + +import maya.cmds as __mc +from PySide2 import QtWidgets as __QTW + + +def __getMayaOS(): + """Get Maya version and parent OS""" + maya = str(__mc.about(api=1))[:4] + os = str(__mc.about(os=1)) + return [int(maya), os] + + +MAYA_VER = __getMayaOS()[0] +OS = __getMayaOS()[1] + +DEBUG = True + +VERSION = __B64.b64decode(b"R1MgQ3VydmVUb29scyB2MS4zLjEKU3R1ZGlvIEVkaXRpb24=").decode('utf-8') + +try: + YEAR = __DT.now().year +except BaseException: + YEAR = 2023 + +MAIN_WINDOW_NAME = 'GSCT_CurveTools' +MAIN_WINDOW_LABEL = 'GS CurveTools' +CURVE_CONTROL_NAME = 'GSCT_CurveControl' +CURVE_CONTROL_LABEL = 'GS Curve Control' +UV_EDITOR_NAME = 'GSCT_UVEditor' +UV_EDITOR_LABEL = 'GS Curve Tools UV Editor' +SCALE_FACTOR_UI = 'GSCT_ScaleFactorWindow' + +UI_SCRIPT = ''' +import gs_curvetools.utils.utils as ct_ut +ct_ut.logger.logger.info("Uninitializing Workspace Control") +maya.cmds.evalDeferred(ct_ut.stopUI) +''' + +FIXED_POLICY = __QTW.QSizePolicy(__QTW.QSizePolicy.Fixed, + __QTW.QSizePolicy.Fixed) +PREFERRED_POLICY = __QTW.QSizePolicy(__QTW.QSizePolicy.Preferred, + __QTW.QSizePolicy.Preferred) +EXPANDING_POLICY = __QTW.QSizePolicy(__QTW.QSizePolicy.Expanding, + __QTW.QSizePolicy.Expanding) diff --git a/Scripts/Modeling/Edit/gs_curvetools/core.py b/Scripts/Modeling/Edit/gs_curvetools/core.py new file mode 100644 index 0000000..377896b --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/core.py @@ -0,0 +1,6729 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +import colorsys +import math +import os +import random +import re +from imp import reload + +import maya.api.OpenMaya as om +import maya.cmds as mc +import maya.mel as mel + +from .constants import * +from .utils import gs_math as mt +from .utils import style, utils, wrap +from .utils.wrap import WIDGETS + +reload(utils) +reload(mt) +reload(wrap) +reload(style) + +MESSAGE = utils.logger +LOGGER = utils.logger.logger + + +### Core Classes ### + +class Attributes: + + def __init__(self, name): + self.name = name + self.copyAttributesSourceCurve = None + self.copyUVsSourceCurve = None + + attrList = { + 'lengthDivisions', + 'dynamicDivisions', + 'widthDivisions', + 'Orientation', + 'Twist', + 'invTwist', + 'Width', + 'WidthX', + 'WidthZ', + 'LengthLock', + 'Length', + 'Taper', + 'Profile', + 'curveRefine', + 'autoRefine', + 'curveSmooth', + 'reverseNormals', + 'surfaceNormals', + 'flipUV', + 'moveU', + 'moveV', + 'rotateUV', + 'rotateRootUV', + 'rotateTipUV', + 'scaleU', + 'scaleV', + 'solidify', + 'solidifyThickness', + 'solidifyDivisions', + 'solidifyScaleX', + 'solidifyScaleY', + 'solidifyOffset', + 'solidifyNormals', + 'Axis', + 'AxisFlip', + 'Offset', + 'lineWidth', + 'samplingAccuracy', + 'Magnitude', + 'profileSmoothing', + 'profileMagnitude', + } + + uvAttr = { + 'moveU', + 'moveV', + 'rotateUV', + 'rotateRootUV', + 'rotateTipUV', + 'scaleU', + 'scaleV', + 'flipUV', + } + + checkBoxes = { + 'autoRefine', + 'dynamicDivisions', + 'LengthLock', + 'reverseNormals', + 'solidify', + 'Axis', + 'AxisFlip', + } + + multiInst = { + 'twistCurve', + 'scaleCurve' + } + + graphAttributes = { + 'twistCurve', + 'scaleCurve', + 'profileCurve', + } + + def getAttr(self, inputCurve, excludeUV=False, excludeCheckboxes=False, manualExclude=False): + getAttr = dict() + for attr in self.attrList: + if excludeUV and attr in self.uvAttr: + continue + if excludeCheckboxes and attr in self.checkBoxes: + continue + if manualExclude and attr in manualExclude: + continue + try: + getAttr[attr] = mc.getAttr(inputCurve + '.' + attr) + except Exception as e: + # LOGGER.exception(e) + pass + return getAttr + + def getCheckboxes(self, inputCurve): + checkboxes = dict() + for attr in self.checkBoxes: + try: + checkboxes[attr] = mc.getAttr(inputCurve + '.' + attr) + except BaseException: + pass + return checkboxes + + def getUVs(self, inputCurve): + getUVs = dict() + for attr in self.uvAttr: + try: + getUVs[attr] = mc.getAttr(inputCurve + '.' + attr) + except BaseException: + pass + return getUVs + + def getMultiInst(self, inputCurve): + if mc.attributeQuery('Length', n=inputCurve, ex=1): + try: + node = mc.ls(mc.listHistory(selectPart(2, True, inputCurve), ac=1, il=0), typ='curveWarp')[0] + except BaseException: + return None + returnList = list() + for attr in self.multiInst: + cList = list() + cIndex = mc.getAttr(node + '.' + attr, mi=1) + for i in cIndex: + cList.append(mc.getAttr(node + '.%s[%s]' % (attr, i))[0]) + cList.append(attr) + returnList.append(cList) + return returnList + + @staticmethod + def setMultiInst(targetCurve, inputList): + if mc.attributeQuery('Length', n=targetCurve, ex=1): + geo = selectPart(2, True, targetCurve) + targetNode = mc.ls(mc.listHistory(geo, ac=1, il=0), typ='curveWarp')[0] + attribute = inputList[-1] + + attributes.resetMultiInst(targetNode, attribute) + + for i in range(len(inputList) - 1): + mc.setAttr(targetNode + '.%s[%s]' % (attribute, i), inputList[i][0], inputList[i][1]) + + @staticmethod + def resetMultiInst(node, attr): + index = mc.getAttr(node + '.%s' % attr, mi=1) + for i in index: + if i == 0: + continue + mc.removeMultiInstance(node + '.%s[%s]' % (attr, i)) + for i in range(4): + mc.setAttr(node + '.%s[%s]' % (attr, i), i / 3.0, 0.5, typ='double2') + + @staticmethod + def blendMultInst(source, target, ratio): # TODO: Add better support for different number of points + returnList = list() + sourceL = len(source) + targetL = len(target) + if sourceL == targetL: + for i in range(sourceL - 1): + returnList.append((source[i][0], mt.lerp(ratio, source[i][1], target[i][1]))) + elif sourceL < targetL: + returnList.append((source[0][0], mt.lerp(ratio, source[0][1], target[0][1]))) + for i in range(1, sourceL - 2): + returnList.append((source[i][0], mt.lerp(ratio, source[i][1], target[i][1]))) + returnList.append((source[-2][0], mt.lerp(ratio, source[-2][1], target[-2][1]))) + else: + returnList.append((source[0][0], mt.lerp(ratio, source[0][1], target[0][1]))) + for i in range(1, targetL - 2): + returnList.append((source[i][0], mt.lerp(ratio, source[i][1], target[i][1]))) + returnList.append((source[-2][0], mt.lerp(ratio, source[-2][1], target[-2][1]))) + returnList.append(source[-1]) + return returnList + + @staticmethod + def setAttr(inputCurve, inputDict, exclude=None): + for attr in inputDict: + if exclude and attr in exclude: + continue + try: + mc.setAttr(inputCurve + "." + attr, inputDict[attr]) + except BaseException: + pass + + def copyAttributes(self): + """Select the last curve in selection list for attribute copy command""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + self.copyAttributesSourceCurve = None + if not sel: + MESSAGE.warningInView('Select at least one Curve') + return + self.copyAttributesSourceCurve = sel[-1] + mc.headsUpMessage('[Copied]', o=self.copyAttributesSourceCurve, time=1, vp=1) + + def pasteAttributes(self): + """Copy and paste the attributes from the source curve (copyAttributes function) to the target selection list""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one Curve.') + return + if not self.copyAttributesSourceCurve: + MESSAGE.warningInView('No copied attributes available.') + return + if not mc.objExists(self.copyAttributesSourceCurve): + MESSAGE.warningInView('Original curve was not found, copying cancelled.') + self.copyAttributesSourceCurve = None + return + sourceAttr = self.getAttr(self.copyAttributesSourceCurve, excludeUV=True) + filterAttrs = dict(eval(mc.optionVar(q='GSCT_AttributesFilter'))) + filteredSourceAttrs = sourceAttr.copy() + for attr in filterAttrs: + if filterAttrs and attr in filterAttrs and not filterAttrs[attr]: + filteredSourceAttrs.pop(attr, None) + multiInd = None + if 'Length' in sourceAttr: + multiInd = self.getMultiInst(self.copyAttributesSourceCurve) + for attr in multiInd: + if filterAttrs and attr[-1] in filterAttrs and not filterAttrs[attr[-1]]: + continue + for target in sel: + self.setMultiInst(target, attr) + for target in sel: + self.setAttr(target, filteredSourceAttrs) + if filterAttrs and 'profileCurve' in filterAttrs and filterAttrs['profileCurve']: + transferProfileCurve(self.copyAttributesSourceCurve, target) + LOGGER.info('Attributes transferred from "%s" to %s target curve(s).' % (self.copyAttributesSourceCurve, len(sel))) + curveControlUI.updateUI() + + def transferAttr(self, hk=None): # Transfer settings from curve to curve + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel or len(sel) < 2: + MESSAGE.warningInView('Select at least two Curves') + return -1 + # Init Vars + source = str() + targets = list() + # Get modifier + mod = True if hk == 2 or (hk is None and utils.getMod() == 'Shift') else False + # Set source and targets + if mod: + source = sel[-1] + targets = sel[0:-1] + else: + source = sel[0] + targets = sel[1:] + mc.headsUpMessage('[Source]', o=source, time=1, vp=1) + sourceAttr = self.getAttr(source, 1) + filterAttrs = dict(eval(mc.optionVar(q='GSCT_AttributesFilter'))) + filteredSourceAttrs = sourceAttr.copy() + for attr in filterAttrs: + if filterAttrs and attr in filterAttrs and not filterAttrs[attr]: + filteredSourceAttrs.pop(attr, None) + multiInd = None + if 'Length' in sourceAttr: + multiInd = self.getMultiInst(source) + for attr in multiInd: + if filterAttrs and attr[-1] in filterAttrs and not filterAttrs[attr[-1]]: + continue + for target in targets: + self.setMultiInst(target, attr) + for target in targets: + self.setAttr(target, filteredSourceAttrs) + if filterAttrs and 'profileCurve' in filterAttrs and filterAttrs['profileCurve']: + transferProfileCurve(source, target) + LOGGER.info('Attributes transferred from "%s" to %s target curve(s).' % (source, len(targets))) + curveControlUI.updateUI() + + def copyUVs(self): + """Select the last curve in selection list for UVs copy command""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + self.copyUVsSourceCurve = None + if not sel: + MESSAGE.warningInView('Select at least one Curve') + return + self.copyUVsSourceCurve = sel[-1] + mc.headsUpMessage('[Copied]', o=self.copyUVsSourceCurve, time=1, vp=1) + + def pasteUVs(self): + """Copy and paste the UVs from the source curve (copyUVs function) to the target selection list""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one Curve.') + return + if not self.copyUVsSourceCurve: + MESSAGE.warningInView('No copied UVs available.') + return + if not mc.objExists(self.copyUVsSourceCurve): + MESSAGE.warningInView('Original curve was not found, copying cancelled.') + self.copyUVsSourceCurve = None + return + source = self.copyUVsSourceCurve + targets = sel + self._transferUVs(source, targets) + LOGGER.info('UVs transferred from "' + str(source) + '" to ' + str(len(sel)) + ' target curve(s).') + curveControlUI.updateUI() + if mc.workspaceControl(UV_EDITOR_NAME, q=1, ex=1): + from . import ui + ui.uveditor.updateEditor() + + def transferUV(self, hk=None): # Transfer Curve UVs + # type: (int|bool|None) -> None + """Transfer UVs from last/first selected curve to all others""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel or len(sel) < 2: + MESSAGE.warningInView('Select at least two Curves') + return -1 + source = str() + targets = list() + # Get modifier + mod = True if hk == 2 or (hk is None and utils.getMod() == 'Shift') else False + # Set source and targets + if mod: + source = sel[-1] + targets = sel[0:-1] + else: + source = sel[0] + targets = sel[1:] + mc.headsUpMessage('[Source]', o=source, time=1, vp=1) + self._transferUVs(source, targets) + LOGGER.info('UVs transferred from "' + str(source) + '" to ' + str(len(targets)) + ' target curve(s).') + curveControlUI.updateUI() + if mc.workspaceControl(UV_EDITOR_NAME, q=1, ex=1): + from . import ui + ui.uveditor.updateEditor() + + def _transferUVs(self, source, targets): + # type: (str, list[str]) -> None + """Transfer UVs from source to target""" + # Check if source curve is bound curve + filterAttrs = dict(eval(mc.optionVar(q='GSCT_AttributesFilter'))) + if mc.attributeQuery('gsmessage', n=source, ex=1): + bindMessage = mc.listConnections(source + '.gsmessage', d=1, s=0) + if bindMessage: + source = bindMessage + # Get source UVs + sourceUVs = [] + if isinstance(source, list): + for s in source: + sourceUVs.append(self.getUVs(s)) + else: + sourceUVs.append(self.getUVs(source)) + # Filter source UVs + for uvs in sourceUVs: + for attr in uvs: + if filterAttrs and attr in filterAttrs and not filterAttrs[attr]: + uvs.pop(attr, None) + for target in targets: + # Check if target is a bound curve + bindMessage = None + if mc.attributeQuery('gsmessage', n=target, ex=1): + bindMessage = mc.listConnections(target + '.gsmessage', d=1, s=0) + if bindMessage: + target = bindMessage + if len(target) == len(sourceUVs): + for i, t in enumerate(target): + self.setAttr(t, sourceUVs[i]) + else: + for t in target: + self.setAttr(t, sourceUVs[-1]) + else: + self.setAttr(target, sourceUVs[-1]) + + def deleteAttr(self, inputCurve): # Delete Attributes from Curve + attrDict = self.attrList + for attr in attrDict: + if mc.attributeQuery(attr, n=inputCurve, ex=1): + mc.deleteAttr(inputCurve + '.' + attr) + + def storeGraphs(self, graph, *_): + """ Store graphs values on curves """ + graphName = graph.objName + graphString = WIDGETS[graphName].getGraph() + selection = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not selection: + selection = mc.filterExpand(mc.listRelatives(mc.ls(sl=1, o=1), p=1, pa=1), sm=9) + if not selection: + return + for sel in selection: + if not mc.attributeQuery(graphName, n=sel, ex=1): + mc.addAttr(sel, ln=graphName, dt='string', k=0) + mc.setAttr(sel + '.' + graphName, graphString, type='string') + + def propagateGraphs(self, graph): + """ Copy the graph changes to other selected curves """ + graphName = graph.objName.replace("_large", "") + selection = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not selection: + selection = mc.filterExpand(mc.listRelatives(mc.ls(sl=1, o=1), p=1, pa=1), sm=9) + if not selection: + return + sourceAttr = self.getAttr(selection[-1], 1) + if 'Length' in sourceAttr: + multiInd = self.getMultiInst(selection[-1]) + for sel in selection: + for attr in multiInd: + if attr[-1] == graphName: + self.setMultiInst(sel, attr) + + +attributes = Attributes("attributes") + + +class Create: + + def __init__(self, name): + self.name = name + self.globalThickness = mc.optionVar(q='GSCT_globalCurveThickness') + self.sf = 1.0 + self.nurbsTesselate = None + self.curveWarp = None + self.polyMoveUV_mid = None + self.polyMoveUV_tip = None + self.polyMoveUV_root = None + self.solidifyNode = None + self.solidifyChoice = None + self.extrude = None + self.lattice = None + self.twist = None + self.magnExpr = None + self.scaleExpr = None + self.uvExpr = None + self.polyNormalNode = None + self.polySoftEdge = None + self.autoRefineCalculate = None + + def currentLayerInd(self): + return WIDGETS['LayerGroup'].checkedId() * -1 - 2 + + def initialize(self): + self.__init__(self.name) + # Check if color mode is enabled + if utils.getAttr('gsColorShaderStorageNode', 'colorApplied'): + toggleColor.updateColors() + self.sf = getScaleFactor() + + # Check if layer is empty + ind = self.currentLayerInd() + self.collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + layer, _, _ = utils.getFormattedLayerNames(self.collectionID, ind) + if mc.objExists(layer): + test = mc.editDisplayLayerMembers(layer, q=1, nr=1, fn=1) + if not test: + deleteUnusedLayers() + + ### Common Creation Methods ### + + def new(self, mode, hk=None): + # type: (int, bool|None) -> None + """Creates a new card or tube of specified type""" + self.initialize() + warpRadio = WIDGETS['warpSwitch'].isChecked() + if warpRadio or hk: + mode = mode + 2 + pathCurve = mc.curve(d=3, + p=[(0, -0.0001, 0), (1.666667 * self.sf, 0, 0), (5 * self.sf, 0, 0), (10 * self.sf, 0, 0), + (15 * self.sf, 0, 0), (18.333333 * self.sf, 0, 0), (20 * self.sf, 0, 0)], + k=[0, 0, 0, 1, 2, 3, 4, 4, 4], n='pathCurve_inst#') + mc.rebuildCurve(pathCurve, kr=2) + finalCurve = [] + if mode == 0: + finalCurve = self.extrudeCard(pathCurve, False) + elif mode == 1: + finalCurve = self.extrudeTube(pathCurve, False) + elif mode == 2: + finalCurve = self.warpCard(pathCurve, False) + elif mode == 3: + finalCurve = self.warpTube(pathCurve, False) + else: + mc.delete(pathCurve) + LOGGER.error("Wrong mode parameter value") + raise ValueError("Wrong mode parameter value") + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + mc.select(finalCurve) + + def multiple(self, mode, hk=False, progressBar=True, keepAttrs=False): + # type: (int, bool, bool, bool) -> None + """Creates Curves from multiple curves selected""" + self.initialize() + selPool = mc.filterExpand(mc.ls(sl=1, l=1, tr=1), sm=9) + try: + selPool = utils.convertInstanceToObj(selPool) + except BaseException: + pass + + if selPool == -1: + return 0 + + if WIDGETS['warpSwitch'].isChecked() or hk: + mode = mode + 2 + + if mode == 0: + create = 'extrudeCards' + elif mode == 1: + create = 'extrudeTubes' + elif mode == 2: + create = 'warpCards' + elif mode == 3: + create = 'warpTubes' + else: + LOGGER.error("Wrong mode parameter value") + raise ValueError("Wrong mode parameter value") + progress = None + allCurves = list() + + if not selPool: + MESSAGE.warningInView('Select at least one curve') + return + + if progressBar: + progress = utils.ProgressBar('Creating ' + create, len(selPool)) + + for pathInst in selPool: + if progress and progress.tick(1): + break + prevAttrs = attributes.getAttr(pathInst) + graphAttrs = {} + for attr in attributes.graphAttributes: + if mc.attributeQuery(attr, n=pathInst, ex=1): + graphAttrs[attr] = mc.getAttr(pathInst + '.' + attr) + if utils.attrExists(pathInst, 'lengthDivisions'): + if mc.connectionInfo(pathInst + '.lengthDivisions', isSource=1): + LOGGER.info('%s is not a compatible curve. Skipped.' % pathInst) + continue + attributes.deleteAttr(pathInst) + if utils.attrExists(pathInst, 'Axis'): + if mc.connectionInfo(pathInst + '.Axis', isSource=1): + LOGGER.info('%s is not a compatible curve. Skipped.' % pathInst) + continue + attributes.deleteAttr(pathInst) + pathInst = mc.rename(pathInst, 'pathCurve_inst#') + returnCurve = list() + + # Bezier curve auto-check + pathInst = utils.checkIfBezier(pathInst) + + if mode == 0: + returnCurve = self.extrudeCard(pathInst) + elif mode == 1: + returnCurve = self.extrudeTube(pathInst) + elif mode == 2: + returnCurve = self.warpCard(pathInst) + elif mode == 3: + returnCurve = self.warpTube(pathInst) + if returnCurve: + if WIDGETS['keepCurveAttributes'].isChecked() and not keepAttrs: + for attr in graphAttrs: + values = graphAttrs[attr] + if attr == 'profileCurve': + updateLattice(values, returnCurve) + else: + rebuildCurve = mc.listConnections(returnCurve + '.curveSmooth')[0] + warp = mc.listConnections(rebuildCurve + '.outputCurve', et=True, t='curveWarp') + if warp: + graphValues = utils.fromStringToDouble2(values) + utils.setDouble2Attr(warp[0], attr, graphValues) + attributes.setAttr(returnCurve, prevAttrs) + allCurves.append(returnCurve) + if progress: + progress.end() + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + mc.select(allCurves, r=1) + return allCurves + + def populate(self, mode, hk=None): # + # type: (int, bool) -> None + """Creates new Curves in between existing curves""" + self.initialize() + sel = mc.filterExpand(mc.ls(sl=1, l=1, dag=1, tr=1), sm=9) + + if not sel or len(sel) < 2: + MESSAGE.warningInView('Select at least two curves') + return 0 + + topGrp = mc.listRelatives(mc.listRelatives(sel[0], p=1, pa=1), p=1, pa=1) + + if WIDGETS['warpSwitch'].isChecked(): + mode = mode + 2 + + # Get original shader + nodes = mc.listHistory(mc.listRelatives(sel[0], c=1, pa=1), f=1) + shapes = mc.ls(nodes, s=1, l=1) + shapeFilter = list() + if len(nodes) > 0: + for node in nodes: + shadingEngine = mc.listConnections(node, c=True, d=True, t='shadingEngine') + if shadingEngine: + break + shapeFilter = mc.ls(shadingEngine, et='shadingEngine') + + loft = mc.loft(sel, c=0, ch=1, d=3, ss=4, rsn=True, ar=1, u=1, rn=0, po=0) + + step = 1.0 / (mc.intSliderGrp('gsCurvesSlider', q=1, value=1) + 1.0) + cond = len(sel) - 1 + allCurves = list() + i = 0 + progress = utils.ProgressBar('Adding Cards', cond * 100) + while i < cond: + if progress.tick(step * 100): + break + roundI = round(i, 1) + crv0 = int(math.floor(roundI)) + crv = int(math.ceil(roundI)) + mod = math.fmod(roundI, 1) + if mod != 0: # Exclude whole numbers + + # Create curve from loft and convert it to curveCard + pathInst = mc.duplicateCurve((loft[0] + '.v[' + str(i) + ']'), rn=0, ch=0, local=0, n='pathCurve_inst#') + mc.rebuildCurve(pathInst, kr=2, kcp=1) + + # Get original scale factor for source curve + interpScaleFactor = scaleFactor0 = scaleFactor1 = getScaleFactor() + if WIDGETS['populateBlendAttributes'].isChecked(): + if mc.attributeQuery('scaleFactor', n=sel[crv0], ex=1): + scaleFactor0 = mc.getAttr(sel[crv0] + '.scaleFactor') + # Get original scale factor for target curve + if mc.attributeQuery('scaleFactor', n=sel[crv], ex=1): + scaleFactor1 = mc.getAttr(sel[crv] + '.scaleFactor') + # Interpolate scale factor + interpScaleFactor = mt.lerp(i - crv0, scaleFactor0, scaleFactor1) + + if interpScaleFactor: + mc.addAttr(pathInst[0], ln='scaleFactor', at='double', dv=interpScaleFactor) + + newCurve = str() + if mode == 0: + newCurve = self.extrudeCard(pathInst[0]) + elif mode == 1: + newCurve = self.extrudeTube(pathInst[0]) + elif mode == 2: + newCurve = self.warpCard(pathInst[0]) + elif mode == 3: + newCurve = self.warpTube(pathInst[0]) + + if WIDGETS['populateBlendAttributes'].isChecked(): + + # Getting all available attributes + crv0Attr = attributes.getAttr(sel[crv0]) + crv0ScaleX = mc.getAttr(sel[crv0] + '.scaleX') + + sourceMultInd = None + if 'Length' in crv0Attr and 'Taper' in crv0Attr and (mode == 2 or mode == 3): + sourceMultInd = attributes.getMultiInst(sel[crv0]) + + # Blending + mod = True if hk == 2 or (hk is None and utils.getMod() == 'Shift') else False + if not mod: + crvAttr = attributes.getAttr(sel[crv], 1, 1) + crv1ScaleX = mc.getAttr(sel[crv] + '.scaleX') + crv0ScaleX = mt.lerp(i - crv0, crv0ScaleX, crv1ScaleX) + for attr in crv0Attr: + if attr in crvAttr: + crv0Attr[attr] = mt.lerp(i - crv0, crv0Attr[attr], crvAttr[attr]) + if 'Length' in crvAttr and 'Taper' in crvAttr and (mode == 2 or mode == 3): + targetMultInd = attributes.getMultiInst(sel[crv]) + if sourceMultInd: + for attr in range(len(sourceMultInd)): + blendMultInst = attributes.blendMultInst(sourceMultInd[attr], + targetMultInd[attr], + i - crv0) + attributes.setMultiInst(newCurve, blendMultInst) + + profileCurve = blendProfileCurve(sel[crv0], sel[crv], i - crv0) + if profileCurve: + updateLattice(profileCurve, newCurve) + + # Applying Settings + attributes.setAttr(newCurve, crv0Attr, ['Width', 'WidthX', 'WidthZ']) + + if mc.attributeQuery('Width', n=newCurve, ex=1): + newWidth = 1 + if 'Width' in crv0Attr: + newWidth = crv0ScaleX * crv0Attr['Width'] + elif 'WidthX' in crv0Attr: + newWidth = (crv0Attr['WidthX'] + crv0Attr['WidthZ']) / 2 * crv0ScaleX + mc.setAttr(newCurve + '.Width', newWidth) + elif mc.attributeQuery('WidthX', n=newCurve, ex=1): + newWidthX = 1 + newWidthZ = 1 + if 'Width' in crv0Attr: + newWidthX = crv0ScaleX * crv0Attr['Width'] + newWidthZ = newWidthX + elif 'WidthX' in crv0Attr: + newWidthX = crv0ScaleX * crv0Attr['WidthX'] + newWidthZ = crv0ScaleX * crv0Attr['WidthZ'] + mc.setAttr(newCurve + '.WidthX', newWidthX) + mc.setAttr(newCurve + '.WidthZ', newWidthZ) + + try: + if shapes[1]: # Applying Shader + newSel = mc.listRelatives(newCurve, c=1, pa=1) + newHist = mc.listHistory(newSel[0], f=1) + newShape = mc.ls(newHist, shapes=1, l=1) + mc.sets(newShape, forceElement=shapeFilter[0], nw=1) + except Exception as e: + LOGGER.exception(e) + + allCurves.append(newCurve) + i += step + progress.end() + mc.delete(loft[0]) + + # Adding to group if exists + if topGrp: + mc.select(cl=1) + for curve in allCurves: + curveGrp = mc.listRelatives(curve, p=1, pa=1)[0] + mc.parent(curveGrp, topGrp) + mc.select(allCurves, r=1) + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + resetCurvePivotPoint() + + def fill(self, hk=None): + # type: (int) -> None + """Place curve duplicates in-between selected curves""" + self.initialize() + sel = mc.filterExpand(mc.ls(sl=1, l=1, dag=1, tr=1), sm=9) + if not sel or len(sel) < 2: + MESSAGE.warningInView('Select at least two curves') + return 0 + loft = mc.loft(sel, c=0, ch=1, d=3, ss=4, rsn=True, ar=1, u=1, rn=0, po=0) + step = 1.0 / (mc.intSliderGrp('gsCurvesSlider', q=1, value=1) + 1.0) + cond = len(sel) - 1 + allCurves = [] + i = 0 + progress = utils.ProgressBar('Adding Cards', cond * 100) + while i < cond: + if progress.tick(step * 100): + break + roundI = round(i, 1) + crv0 = int(math.floor(roundI)) + crv = int(math.ceil(roundI)) + mod = math.fmod(roundI, 1) + if mod != 0: # Exclude whole numbers + # Create curve from loft + pathInst = mc.duplicateCurve('%s.v[%s]' % (loft[0], i), rn=0, ch=0, local=0, n='pathCurve_inst#') + mc.rebuildCurve(pathInst, kr=2, s=mc.getAttr(sel[crv0] + '.spans')) + targetPoints = mc.getAttr(pathInst[0] + '.cp[:]') + mc.delete(pathInst) + newCurve = duplicateCurve(customSel=[sel[crv0]]) + if newCurve: + newCurve = newCurve[0] + else: + MESSAGE.warningInView('Wrong Selection Detected: %s' % sel[crv0]) + break + for p in range(0, len(targetPoints)): + mc.xform('%s.cp[%s]' % (newCurve, p), t=targetPoints[p], wd=1, ws=1) + + # Get Attributes + previousAttr = attributes.getAttr(sel[crv0], excludeCheckboxes=True, excludeUV=True) + targetAttr = attributes.getAttr(sel[crv], excludeCheckboxes=True, excludeUV=True) + newAttr = previousAttr + + previousMultiInst = attributes.getMultiInst(sel[crv0]) + targetMultiInst = attributes.getMultiInst(sel[crv]) + blendMultiInst = [] + + modifier = True if hk == 2 or (hk is None and utils.getMod() == 'Shift') else False + if not modifier: + # Interpolate Attributes + for attr in previousAttr: + if attr in targetAttr: + newAttr[attr] = mt.lerp(i - crv0, previousAttr[attr], targetAttr[attr]) + + if previousMultiInst and targetMultiInst and (len(previousMultiInst) == len(targetMultiInst)): + for attr in range(len(previousMultiInst)): + blendMultiInst.append(attributes.blendMultInst(previousMultiInst[attr], targetMultiInst[attr], i - crv0)) + + profileCurve = blendProfileCurve(sel[crv0], sel[crv], i - crv0) + if profileCurve: + updateLattice(profileCurve, newCurve) + + # Apply Attributes + attributes.setAttr(newCurve, newAttr) + allCurves.append(newCurve) + + for attr in blendMultiInst: + attributes.setMultiInst(newCurve, attr) + + i += step + mc.delete(loft) + progress.end() + mc.select(allCurves, r=1) + resetCurvePivotPoint(customCurves=allCurves) + + ### Curve Extrude Creation Methods ### + + def extrudeCard(self, pathInst, auto=True): + # type: (str, bool) -> str + """Creates Curve Card from pathInst""" + self.pathInst = mc.filterExpand(pathInst, sm=9)[0] + + if not self.pathInst: + MESSAGE.warningInView('Select nurbs or bezier curve') + return 0 + + spans = mc.getAttr(self.pathInst + '.spans') + self.ldiv = 10 + self.refine = 20 + if auto: + self.ldiv = spans * 2 if spans <= 10 else spans + self.refine = 20 if spans <= 20 else spans + + # Breaking if connection exist + try: + mc.disconnectAttr(self.pathInst + '.sx', self.pathInst + '.sy') + except BaseException: + pass + try: + mc.disconnectAttr(self.pathInst + '.sx', self.pathInst + '.sz') + except BaseException: + pass + + # Checking the stored scale factor + scaleFactor = self.sf + if mc.attributeQuery('scaleFactor', n=self.pathInst, ex=1) and WIDGETS['keepCurveAttributes'].isChecked(): + scaleFactor = mc.getAttr(self.pathInst + '.scaleFactor') + + # Creating profile + self.profileInst = mc.curve( + p=[(0, 0, -2.5 * scaleFactor), (0, 0.5 * scaleFactor, -1.666667 * scaleFactor), (0, 1 * scaleFactor, 0), + (0, 0.5 * scaleFactor, 1.666667 * scaleFactor), (0, 0, 2.5 * scaleFactor)], k=[0, 0, 0, 1, 2, 2, 2], d=3, + n='profileCurve_inst#') + mc.makeIdentity(self.pathInst, n=0, s=1, r=1, t=1, apply=True, pn=1) + curveOrigin = mc.pointPosition(self.pathInst + '.cv[0]', w=1) + mc.xform(self.pathInst, ws=1, piv=(curveOrigin[0], curveOrigin[1], curveOrigin[2])) + mc.move(0, 0, 0, self.pathInst, rpr=1, ws=1) + mc.makeIdentity(self.pathInst, n=0, s=1, r=1, t=1, apply=True, pn=1) + mc.nurbsToPolygonsPref(polyType=1, vType=1, format=2, uType=1, vNumber=10, uNumber=3) + self.hairCard = mc.extrude(self.profileInst, self.pathInst, upn=1, ch=True, rotation=0, ucp=1, n='geoCard#', fpt=1, scale=1, + et=2, rn=False, rsp=1, po=1)[0] + + # Turning off inherit transform + mc.setAttr(self.hairCard + '.inheritsTransform', 0) + + mc.xform(self.hairCard, t=(curveOrigin[0], curveOrigin[1], curveOrigin[2])) + + # Create actual path curve + self.pathCurve = str(mc.rename(mc.duplicate(self.pathInst, ilf=1, rr=1)[0], 'pathCurve#')) + pathInstShape = mc.ls(self.pathInst, s=1, dag=1, l=1)[0] + self.nurbsTesselate = mc.listConnections(mc.ls(self.hairCard, s=1, dag=1, l=1), s=True, d=False)[0] + self.extrude = mc.listConnections(self.nurbsTesselate, s=True, d=False)[0] + mc.setAttr(self.extrude + '.useProfileNormal', 1) + + # Connecting + + mc.connectAttr(self.pathCurve + '.translate', self.hairCard + '.translate', f=1) + mc.connectAttr(self.pathCurve + '.rotate', self.hairCard + '.rotate', f=1) + mc.connectAttr(self.pathCurve + '.scale', self.hairCard + '.scale', f=1) + mc.connectAttr(self.pathCurve + '.scaleX', self.pathCurve + '.scaleY', f=1) + mc.connectAttr(self.pathCurve + '.scaleX', self.pathCurve + '.scaleZ', f=1) + mc.connectAttr(self.pathCurve + '.rotatePivot', self.hairCard + '.rotatePivot', f=1) + mc.connectAttr(self.pathCurve + '.scalePivot', self.hairCard + '.scalePivot', f=1) + mc.connectAttr(self.pathCurve + '.rotatePivotTranslate', self.hairCard + '.rotatePivotTranslate', f=1) + mc.connectAttr(self.pathCurve + '.scalePivotTranslate', self.hairCard + '.scalePivotTranslate', f=1) + mc.connectAttr(pathInstShape + '.editPoints[0]', self.profileInst + '.translate', f=1) + mc.connectAttr(pathInstShape + '.editPoints[0]', self.extrude + '.pivot', f=1) + + # Moving path curve to original position + + mc.xform(self.pathCurve, ws=1, t=(curveOrigin[0], curveOrigin[1], curveOrigin[2])) + + # Attribute addition + mc.addAttr(self.pathCurve, ln='lengthDivisions', dv=self.ldiv, smx=500, at='long', min=2, k=1) + mc.addAttr(self.pathCurve, ln='widthDivisions', dv=3, smx=11, at='long', min=2, k=1) + mc.addAttr(self.pathCurve, ln='Orientation', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='Twist', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='Width', dv=1, smx=5, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln='Taper', dv=1, smx=5, at='double', min=0, k=1) + mc.addAttr(self.pathCurve, ln='Profile', smn=-2, dv=0, at='double', smx=2, k=1) + mc.connectAttr(self.pathCurve + '.lengthDivisions', (self.nurbsTesselate + '.vNumber'), f=1) + mc.connectAttr(self.pathCurve + '.widthDivisions', (self.nurbsTesselate + '.uNumber'), f=1) + mc.connectAttr(self.pathCurve + '.Orientation', (self.profileInst + '.rotateX'), f=1) + mc.connectAttr(self.pathCurve + '.Twist', (self.extrude + '.rotation'), f=1) + mc.connectAttr(self.pathCurve + '.Width', (self.profileInst + '.scaleZ'), f=1) + mc.connectAttr(self.pathCurve + '.Taper', (self.extrude + '.scale'), f=1) + mc.connectAttr(self.pathCurve + '.Profile', (self.profileInst + '.scaleY'), f=1) + + # Add Modules + + self.addRefine() + self.addAutoRefine() + self.addPolyNormal() + self.addUVs() + self.addSolidify() + self.addMessage() + self.addScaleFactor() + self.addDynamicDivisions() + self.setCurveThickness() + self.hideNodes() + self.group('curveCard#') + + return (self.hairCardGrp + '|' + self.pathCurve) + + def extrudeTube(self, pathInst, auto=True): # Creates Curve Tube from pathInst + self.pathInst = mc.filterExpand(pathInst, sm=9)[0] + + if not self.pathInst: + MESSAGE.warningInView('Select nurbs or bezier curve') + return 0 + + spans = mc.getAttr(self.pathInst + '.spans') + self.ldiv = 10 + self.refine = 20 + if auto: + self.ldiv = spans * 2 if spans <= 10 else spans + self.refine = 20 if spans <= 20 else spans + + # Breaking if connected + try: + mc.disconnectAttr(self.pathInst + '.sx', self.pathInst + '.sy') + except BaseException: + pass + try: + mc.disconnectAttr(self.pathInst + '.sx', self.pathInst + '.sz') + except BaseException: + pass + + # Checking the stored scale factor + scaleFactor = self.sf + if mc.attributeQuery('scaleFactor', n=self.pathInst, ex=1) and WIDGETS['keepCurveAttributes'].isChecked(): + scaleFactor = mc.getAttr(self.pathInst + '.scaleFactor') + + # Creating a profile + self.profileInst = mc.circle(c=(0, 0, 0), ch=1, d=3, ut=0, sw=360, n="profileCurve_inst#", + s=8, r=1 * scaleFactor, tol=0.01, nr=(0, 1, 0)) + mc.makeIdentity(self.pathInst, n=0, s=1, r=1, t=1, apply=True, pn=1) + curveOrigin = mc.pointPosition(self.pathInst + '.cv[0]', w=1) + mc.xform(self.pathInst, ws=1, piv=(curveOrigin[0], curveOrigin[1], curveOrigin[2])) + mc.move(0, 0, 0, self.pathInst, rpr=1, ws=1) + mc.makeIdentity(self.pathInst, n=0, s=1, r=1, t=1, apply=True, pn=1) + mc.nurbsToPolygonsPref(polyType=1, vType=1, format=2, uType=1, vNumber=10, uNumber=3) + self.hairCard = mc.extrude(self.profileInst[0], self.pathInst, upn=1, ch=True, rotation=0, + ucp=1, n='geoTube#', fpt=1, scale=1, et=2, rn=False, rsp=1, po=1)[0] + + # Turning off inherit transform + mc.setAttr(self.hairCard + '.inheritsTransform', 0) + + mc.xform(self.hairCard, t=(curveOrigin[0], curveOrigin[1], curveOrigin[2])) + self.pathCurve = str(mc.rename(mc.duplicate(self.pathInst, ilf=1, rr=1)[0], 'pathCurve#')) + pathInstShape = mc.ls(self.pathInst, s=1, dag=1, l=1)[0] + self.nurbsTesselate = mc.listConnections(mc.ls(self.hairCard, s=1, dag=1, l=1)[0], s=True, d=False)[0] + self.extrude = mc.listConnections(self.nurbsTesselate, s=True, d=False)[0] + mc.setAttr(self.extrude + '.useProfileNormal', 1) + + # Connecting + + mc.connectAttr(self.pathCurve + '.translate', self.hairCard + '.translate', f=1) + mc.connectAttr(self.pathCurve + '.rotate', self.hairCard + '.rotate', f=1) + mc.connectAttr(self.pathCurve + '.scale', self.hairCard + '.scale', f=1) + mc.connectAttr(self.pathCurve + '.scaleX', self.pathCurve + '.scaleY', f=1) + mc.connectAttr(self.pathCurve + '.scaleX', self.pathCurve + '.scaleZ', f=1) + mc.connectAttr(self.pathCurve + '.rotatePivot', self.hairCard + '.rotatePivot', f=1) + mc.connectAttr(self.pathCurve + '.scalePivot', self.hairCard + '.scalePivot', f=1) + mc.connectAttr(self.pathCurve + '.rotatePivotTranslate', self.hairCard + '.rotatePivotTranslate', f=1) + mc.connectAttr(self.pathCurve + '.scalePivotTranslate', self.hairCard + '.scalePivotTranslate', f=1) + mc.connectAttr(pathInstShape + '.editPoints[0]', self.profileInst[0] + '.translate', f=1) + mc.connectAttr(pathInstShape + '.editPoints[0]', self.extrude + '.pivot', f=1) + + # Moving path curve to original position + + mc.xform(self.pathCurve, ws=1, t=(curveOrigin[0], curveOrigin[1], curveOrigin[2])) + + # Attribute addition + mc.addAttr(self.pathCurve, ln="lengthDivisions", dv=self.ldiv, smx=500, at='long', min=2, k=1) + mc.addAttr(self.pathCurve, ln="widthDivisions", dv=7, smx=53, at='long', min=4, k=1) + mc.addAttr(self.pathCurve, ln="Orientation", smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln="Twist", smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln="WidthX", dv=1, smx=20, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln="WidthZ", dv=1, smx=20, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln="Taper", dv=1, smx=5, at='double', min=0, k=1) + mc.connectAttr(self.pathCurve + ".lengthDivisions", self.nurbsTesselate + ".vNumber", f=1) + mc.connectAttr(self.pathCurve + ".widthDivisions", self.nurbsTesselate + ".uNumber", f=1) + mc.connectAttr(self.pathCurve + ".Orientation", self.profileInst[0] + ".rotateY", f=1) + mc.connectAttr(self.pathCurve + ".Twist", self.extrude + ".rotation", f=1) + mc.connectAttr(self.pathCurve + ".WidthX", self.profileInst[0] + ".scaleX", f=1) + mc.connectAttr(self.pathCurve + ".WidthZ", self.profileInst[0] + ".scaleZ", f=1) + mc.connectAttr(self.pathCurve + ".Taper", self.extrude + ".scale", f=1) + + self.addRefine() + self.addAutoRefine() + self.addPolyNormal() + self.addUVs() + self.addSolidify() + self.addMessage() + self.addScaleFactor() + self.addDynamicDivisions() + self.setCurveThickness() + self.hideNodes() + self.group('curveTube#') + + return (self.hairCardGrp + '|' + self.pathCurve) + + ### Curve Warp Creation Methods ### + + def warpCard(self, pathCurve, auto=True): # Creates New Curve Warp Card from inputCurve + self.pathCurve = mc.filterExpand(pathCurve, sm=9) + if not self.pathCurve: + MESSAGE.warningInView('Select at least one curve') + return 0 + + self.pathCurve = mc.rename(self.pathCurve, 'pathCurve#') + self.pathCurveShape = mc.ls(self.pathCurve, dag=1, s=1, l=1)[0] + spans = mc.getAttr(self.pathCurveShape + '.spans') + self.ldiv = 10 + self.refine = 20 + if auto: + self.ldiv = spans * 2 if spans <= 10 else spans + self.refine = 20 if spans <= 20 else spans + + # Breaking + try: + mc.disconnectAttr(self.pathCurve + '.sx', self.pathCurve + '.sy') + except BaseException: + pass + try: + mc.disconnectAttr(self.pathCurve + '.sx', self.pathCurve + '.sz') + except BaseException: + pass + + # Checking the stored scale factor + scaleFactor = self.sf + if mc.attributeQuery('scaleFactor', n=self.pathCurve, ex=1) and WIDGETS['keepCurveAttributes'].isChecked(): + scaleFactor = mc.getAttr(self.pathCurve + '.scaleFactor') + + # Creating path and profile curves + self.pathInst = mc.curve(d=3, p=[(0, -0.0001, 0), (1.666667 * scaleFactor, 0, 0), (5 * scaleFactor, 0, 0), + (10 * scaleFactor, 0, 0), (15 * scaleFactor, 0, 0), (18.333333 * scaleFactor, 0, 0), + (20 * scaleFactor, 0, 0)], k=[0, 0, 0, 1, 2, 3, 4, 4, 4], n='pathCurve_inst#') + mc.rebuildCurve(self.pathInst, s=30) + + self.profileInst = mc.curve( + p=[(0, 0, -2.5 * scaleFactor), (0, 0.5 * scaleFactor, -1.666667 * scaleFactor), (0, 1 * scaleFactor, 0), + (0, 0.5 * scaleFactor, (1.666667 * scaleFactor)), (0, 0, (2.5 * scaleFactor))], k=[0, 0, 0, 1, 2, 2, 2], d=3, + n='profileCurve_inst#') + + mc.setAttr(self.profileInst + '.sy', 4) + + mc.nurbsToPolygonsPref(polyType=1, vType=1, format=2, uType=1, vNumber=10, uNumber=3) + extrudeCmd = mc.extrude(self.profileInst, self.pathInst, upn=1, ch=True, rotation=0, ucp=1, n='geoCard#', fpt=1, + scale=1, et=2, rn=False, rsp=1, po=1) + + self.hairCard = extrudeCmd[0] + self.extrude = extrudeCmd[1] + self.nurbsTesselate = mc.listConnections(self.extrude, s=False, d=True, et=1, t='nurbsTessellate')[0] + + # Creating deformers + + latticeDivisions = 10 + + self.lattice = mc.lattice(self.hairCard, dv=[latticeDivisions, 2, 2], oc=True, ldv=[2, 2, 2], ol=1) + mc.move(0, 0, 0, self.lattice[1] + '.scalePivot', self.lattice[1] + '.rotatePivot', a=1) + + points = [] + for i in range(latticeDivisions): + points.append(mc.pointPosition(self.lattice[1] + '.pt[%s][1][0]' % (i), l=1)) + + self.twist = mc.nonLinear(self.hairCard, type='twist') + mc.setAttr(self.twist[1] + '.rz', 90) + mc.move(0, 0, 0, self.twist, a=1, y=1) + + # Creating warp + + self.curveWarp = mc.deformer(ignoreSelected=1, type='curveWarp', n='curveWarp#')[0] + mc.deformer(self.curveWarp, e=1, g=self.hairCard) + mc.connectAttr(self.pathCurveShape + '.worldSpace[0]', self.curveWarp + '.inputCurve') + mc.setAttr(self.curveWarp + '.alignmentMode', 2) + + # Turning off inherit transform + + mc.setAttr(self.hairCard + '.inheritsTransform', 0) + mc.setAttr(self.extrude + '.useProfileNormal', 1) + if MAYA_VER >= 2018: + mc.setAttr(self.curveWarp + '.samplingAccuracy', 0.333) + mc.addAttr(self.pathCurve, ln='samplingAccuracy', dv=0.333, smx=2, at='double', min=0.001, k=1) + mc.connectAttr(self.pathCurve + '.samplingAccuracy', self.curveWarp + '.samplingAccuracy') + mc.setAttr(self.curveWarp + '.twistRotation', 720) + + # Adding and connecting main attributes + + mc.addAttr(self.pathCurve, ln='lengthDivisions', dv=self.ldiv, smx=500, at='long', min=2, k=1) + mc.addAttr(self.pathCurve, ln='widthDivisions', dv=3, smx=11, at='long', min=2, k=1) + mc.addAttr(self.pathCurve, ln='Orientation', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='Twist', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='invTwist', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='Width', dv=1, smx=5, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln='LengthLock', at='enum', dv=0, en="Locked:Unlocked:", k=1) + mc.addAttr(self.pathCurve, ln='Length', dv=1, smx=30, smn=-30, at='double', k=1) + mc.addAttr(self.pathCurve, ln='Offset', dv=0, smx=1, smn=-1, at='double', min=-30, max=30, k=1) + mc.addAttr(self.pathCurve, ln='Taper', dv=1, smx=5, at='double', min=0, k=1) + mc.addAttr(self.pathCurve, ln='Profile', smn=-2, dv=0, at='double', smx=2, k=1) + mc.addAttr(self.pathCurve, ln='profileSmoothing', dv=2, at='long', min=2, max=30, k=0) + mc.addAttr(self.pathCurve, ln='profileMagnitude', dv=1, at='double', min=-2, max=2, k=0) + + if not mc.attributeQuery('initialLatticePoints', n=self.pathCurve, ex=1): + mc.addAttr(self.pathCurve, ln='initialLatticePoints', dt='double3', m=1) + for i in range(len(points)): + mc.setAttr(self.pathCurve + '.initialLatticePoints[%s]' % i, *points[i], type='double3') + + if not mc.attributeQuery('latticeMessage', n=self.pathCurve, ex=1): + mc.addAttr(self.pathCurve, ln='latticeMessage', at='message', k=0) + mc.addAttr(self.lattice[1], ln='latticeMessage', at='message', k=0) + + mc.connectAttr(self.pathCurve + '.lengthDivisions', self.nurbsTesselate + '.vNumber', f=1) + mc.connectAttr(self.pathCurve + '.widthDivisions', self.nurbsTesselate + '.uNumber', f=1) + mc.connectAttr(self.pathCurve + '.Twist', self.twist[0] + '.startAngle', f=1) + mc.connectAttr(self.pathCurve + '.invTwist', self.twist[0] + '.endAngle', f=1) + mc.connectAttr(self.pathCurve + '.Width', self.profileInst + '.scaleZ', f=1) + mc.connectAttr(self.pathCurve + '.Taper', self.extrude + '.scale', f=1) + mc.connectAttr(self.pathCurve + '.Profile', self.profileInst + '.scaleY', f=1) + mc.connectAttr(self.pathCurve + '.LengthLock', self.curveWarp + '.keepLength', f=1) + mc.connectAttr(self.pathCurve + '.Length', self.curveWarp + '.lengthScale', f=1) + mc.connectAttr(self.pathCurve + '.Offset', self.curveWarp + '.offset', f=1) + mc.connectAttr(self.pathCurve + '.latticeMessage', self.lattice[1] + '.latticeMessage', f=1) + mc.connectAttr(self.pathCurve + '.profileSmoothing', self.lattice[0] + '.localInfluenceS', f=1) + mc.connectAttr(self.pathCurve + '.profileMagnitude', self.lattice[0] + '.envelope', f=1) + + # Fix Twist connections for Maya 2020.4 + twistHandle = mc.listRelatives(self.twist, c=1, pa=1) + connectionCheck = mc.isConnected(self.twist[0] + '.startAngle', twistHandle[0] + '.startAngle') + if not connectionCheck: + mc.connectAttr(self.twist[0] + '.startAngle', twistHandle[0] + '.startAngle', f=1) + mc.connectAttr(self.twist[0] + '.endAngle', twistHandle[0] + '.endAngle', f=1) + + # Fix Lattice connection for Maya 2020.4 + if MAYA_VER == 2020: + ffd = mc.listConnections(self.pathCurve + '.profileMagnitude', s=0, d=1) + origGeo = mc.listConnections(ffd[0] + '.originalGeometry[0]', s=1, d=0, p=1) + mc.disconnectAttr(origGeo[0], ffd[0] + '.originalGeometry[0]') + + # Twist Magnitude and Orientation Sum + + mc.addAttr(self.pathCurve, ln='Magnitude', dv=0.5, at='double', h=1) + ex = ''' + {2}.rx = {1}.Orientation + {1}.rx; + {0}.rotation = 360 * (1 - {1}.Magnitude); + {0}.twistRotation = 720 * {1}.Magnitude; + '''.format(self.curveWarp, self.pathCurve, self.lattice[1]) + self.magnExpr = mc.expression(ae=0, s=ex, n='twistOrienCalc#') + + self.addRefine(True) + self.addAutoRefine() + self.addPolyNormal() + self.addUVs() + self.addSolidify() + self.addMessage() + self.addScaleFactor() + self.addDynamicDivisions() + self.setCurveThickness() + self.hideNodes() + self.clearTweakNode() + self.group('warpCard#') + + return (self.hairCardGrp + '|' + self.pathCurve) + + def warpTube(self, pathCurve, auto=True): # Creates New Curve Warp Tube from inputCurve + self.pathCurve = mc.filterExpand(pathCurve, sm=9)[0] + if not self.pathCurve: + MESSAGE.warningInView('Select at least one curve') + return 0 + self.pathCurve = mc.rename(self.pathCurve, 'pathCurve#') + self.pathCurveShape = mc.ls(self.pathCurve, dag=1, s=1, l=1)[0] + spans = mc.getAttr(self.pathCurveShape + '.spans') + self.ldiv = 10 + self.refine = 20 + if auto: + self.ldiv = spans * 2 if spans <= 10 else spans + self.refine = 20 if spans <= 20 else spans + + try: + mc.disconnectAttr(self.pathCurve + '.sx', self.pathCurve + '.sy') + except BaseException: + pass + try: + mc.disconnectAttr(self.pathCurve + '.sx', self.pathCurve + '.sz') + except BaseException: + pass + + # Checking the stored scale factor + scaleFactor = self.sf + if mc.attributeQuery('scaleFactor', n=self.pathCurve, ex=1) and WIDGETS['keepCurveAttributes'].isChecked(): + scaleFactor = mc.getAttr(self.pathCurve + '.scaleFactor') + + # Creating path and profile curves + self.pathInst = mc.curve(d=3, p=[(0, -0.0001, 0), (1.666667 * scaleFactor, 0, 0), (5 * scaleFactor, 0, 0), + (10 * scaleFactor, 0, 0), (15 * scaleFactor, 0, 0), (18.333333 * scaleFactor, 0, 0), + (20 * scaleFactor, 0, 0)], k=[0, 0, 0, 1, 2, 3, 4, 4, 4], n='pathCurve_inst#') + mc.rebuildCurve(self.pathInst, s=30) + + self.profileInst = mc.circle(c=(0, 0, 0), ch=1, d=3, ut=0, sw=360, n="profileCurve_inst#", s=8, r=1 * scaleFactor, tol=0.01, + nr=(0, 1, 0))[0] + + mc.nurbsToPolygonsPref(polyType=1, vType=1, format=2, uType=1, vNumber=10, uNumber=7) + extrudeCmd = mc.extrude(self.profileInst, self.pathInst, upn=1, ch=True, rotation=0, ucp=1, n='geoTube#', fpt=1, + scale=1, et=2, rn=False, rsp=1, po=1) + + self.hairCard = extrudeCmd[0] + self.extrude = extrudeCmd[1] + self.nurbsTesselate = mc.listConnections(self.extrude, s=False, d=True, et=1, t='nurbsTessellate')[0] + + # Creating curveWarp deformer + + self.curveWarp = mc.deformer(ignoreSelected=1, type='curveWarp', n='curveWarp#')[0] + mc.deformer(self.curveWarp, e=1, g=self.hairCard) + mc.connectAttr(self.pathCurveShape + '.worldSpace[0]', self.curveWarp + '.inputCurve') + + # Turning off inherit transform + + mc.setAttr(self.hairCard + '.inheritsTransform', 0) + mc.setAttr(self.extrude + '.useProfileNormal', 1) + + if MAYA_VER >= 2018: + mc.setAttr(self.curveWarp + '.samplingAccuracy', 0.333) + mc.addAttr(self.pathCurve, ln='samplingAccuracy', dv=0.333, smx=2, at='double', min=0.001, k=1) + mc.connectAttr(self.pathCurve + '.samplingAccuracy', self.curveWarp + '.samplingAccuracy') + + mc.setAttr(self.curveWarp + '.twistRotation', 720) + + # Adding and connecting main attributes + + mc.addAttr(self.pathCurve, ln='lengthDivisions', dv=self.ldiv, smx=500, at='long', min=2, k=1) + mc.addAttr(self.pathCurve, ln='widthDivisions', dv=7, smx=53, at='long', min=4, k=1) + mc.addAttr(self.pathCurve, ln='Orientation', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='Twist', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln="WidthX", dv=1, smx=5, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln="WidthZ", dv=1, smx=5, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln='LengthLock', at='enum', dv=0, en="Locked:Unlocked:", k=1) + mc.addAttr(self.pathCurve, ln='Length', dv=1, smx=30, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln='Offset', dv=0, smx=1, smn=-1, at='double', min=-30, max=30, k=1) + mc.addAttr(self.pathCurve, ln='Taper', dv=1, max=1.9, at='double', min=0, k=1) + mc.connectAttr(self.pathCurve + '.lengthDivisions', self.nurbsTesselate + '.vNumber', f=1) + mc.connectAttr(self.pathCurve + '.widthDivisions', self.nurbsTesselate + '.uNumber', f=1) + mc.connectAttr(self.pathCurve + '.Twist', self.extrude + '.rotation', f=1) + mc.connectAttr(self.pathCurve + '.Taper', self.extrude + '.scale', f=1) + mc.connectAttr(self.pathCurve + '.LengthLock', self.curveWarp + '.keepLength', f=1) + mc.connectAttr(self.pathCurve + '.Length', self.curveWarp + '.lengthScale', f=1) + mc.connectAttr(self.pathCurve + '.Offset', self.curveWarp + '.offset', f=1) + + # Twist Magnitude + + mc.addAttr(self.pathCurve, ln='Magnitude', dv=0.5, at='double', h=1) + ex = ''' + {2}.rx = {1}.Orientation + {1}.rx; + {0}.rotation = 360 * (1 - {1}.Magnitude); + {0}.twistRotation = 720 * {1}.Magnitude; + '''.format(self.curveWarp, self.pathCurve, self.profileInst) + self.magnExpr = mc.expression(ae=0, s=ex, n='twistOrienCalc#') + # Handling Width to Scale Switch + + scaleEx = ''' + if(({0}.WidthX >= 5 && {0}.WidthZ >= 5)) + {{ + {1}.scaleZ = 5; + {1}.scaleX = 5; + {2}.maxScale = abs({0}.WidthZ - 5) + abs({0}.WidthX - 5) + 2; + }} + else if (({0}.WidthX >= 5)) + {{ + {1}.scaleX = 5; + {1}.scaleZ = {0}.WidthZ; + {2}.maxScale = abs({0}.WidthX - 5) + 2; + }} + else if (({0}.WidthZ >= 5)) + {{ + {1}.scaleZ = 5; + {1}.scaleX = {0}.WidthX; + {2}.maxScale = abs({0}.WidthZ - 5) + 2; + }} + else + {{ + {2}.maxScale = 2; + {1}.scaleX = {0}.WidthX; + {1}.scaleZ = {0}.WidthZ; + }} + '''.format(self.pathCurve, self.profileInst, self.curveWarp) + self.scaleExpr = mc.expression(ae=0, s=scaleEx, n='scaleManagement#') + + self.addRefine(True) + self.addAutoRefine() + self.addPolyNormal() + self.addUVs() + self.addSolidify() + self.addMessage() + self.addScaleFactor() + self.addDynamicDivisions() + self.setCurveThickness() + self.hideNodes() + self.clearTweakNode() + self.group('warpTube#') + + return (self.hairCardGrp + '|' + self.pathCurve) + + def bind(self, hk=None): # TODO: Check hotkey command # Attaches selected geo to selected curve + self.initialize() + # Sort for geoWarp + self.ldiv = 10 + self.refine = 20 + + sel = mc.ls(sl=1, tr=1) + if not sel or len(sel) < 2: + MESSAGE.warningInView( + 'Select one curve and one geometry or Select one target curve and any number of curveCards/Tubes') + return 0 + + curves = mc.filterExpand(sel, sm=9) + geo = mc.filterExpand(sel, sm=12) + + # Add gsmessage + for crv in curves: + if not mc.attributeQuery('gsmessage', n=crv, ex=1): + mc.addAttr(crv, ln='gsmessage', at='message', k=0) + + if not getOption('massBindOption'): + self.singleBind(sel, geo, hk) + else: + # Filter active curves + activeCurves = [] + inactiveCurves = [] + for curve in curves: + if (mc.attributeQuery('Orientation', n=curve, ex=1) and + mc.connectionInfo(curve + '.Orientation', isSource=1)): + activeCurves.append(curve) + else: + inactiveCurves.append(curve) + if not geo: + progress = utils.ProgressBar('Binding', len(inactiveCurves)) + try: + for i in range(len(inactiveCurves)): + progress.tick(1) + passedCurves = activeCurves + [inactiveCurves[i]] + self.singleBind(passedCurves, None, True) + except Exception as e: + LOGGER.exception(e) + finally: + progress.end() + else: + progress = utils.ProgressBar('Binding', len(inactiveCurves)) + try: + for i in range(len(inactiveCurves)): + progress.tick(1) + passedGeo = mc.duplicate(geo[0]) + passedCurves = [inactiveCurves[i]] + self.singleBind(passedCurves, passedGeo, True) + except Exception as e: + LOGGER.exception(e) + finally: + progress.end() + + def singleBind(self, curves, geometry=None, hk=None): + grpName = 'bindGeo#' + + sel = curves + self.pathCurve = [] + self.geo = [] + + self.pathCurve = mc.filterExpand(curves, sm=9) + self.geo = geometry + + isCurveWarp = False + origGroups = [] + targetCurve = None + sourceCurves = [] + + if not self.geo: + # Sort for curveWarp + isCurveWarp = True + grpName = 'bindGroup#' + for crv in sel: + attr = attributes.getAttr(crv, 1, 1) + if 'Orientation' in attr and mc.connectionInfo(crv + '.Orientation', isSource=1): + sourceCurves.append(crv) + else: + targetCurve = crv + if not sourceCurves or not targetCurve: + MESSAGE.warningInView('Wrong selection') + return 0 + + self.pathCurve = targetCurve + + # Duplicating the source curves if needed + mod = True if hk == 2 or (hk is None and utils.getMod() == 'Shift') else False + if getOption('bindDuplicatesCurves') or getOption('massBindOption') or mod: + sourceCurves = duplicateCurve(sourceCurves) + + # Original Groups + for curve in sourceCurves: + if (mc.attributeQuery('Orientation', n=curve, ex=1) and + mc.connectionInfo(curve + '.Orientation', isSource=1)): + origGroups += mc.listRelatives(curve, p=1, pa=1) + + # Adding source curves to a new group + hairGrp = self.currentLayerInd() + curveAddToLayer(hairGrp, inputCurves=sourceCurves) + + # Connect message + for crv in sourceCurves: + try: + mc.connectAttr(self.pathCurve + ".gsmessage", crv + '.gsmessage', f=1) + except BaseException: + LOGGER.info('"gsmessage" attribute not found. Possibly legacy curve.') + + # Flip original UVs before bind + if getOption('bindFlipUVs'): + for crv in sourceCurves: + if mc.attributeQuery('flipUV', n=crv, ex=1): + mc.setAttr(crv + '.flipUV', not mc.getAttr(crv + '.flipUV')) + + # Center and average pivots + wsPivX = list() + wsPivY = list() + wsPivZ = list() + lsPiv = list() + tr = list() + mc.xform(sourceCurves, cpc=1) + for ele in sourceCurves: + wsPiv = mc.xform(ele, q=1, rp=1, ws=1) + wsPivX.append(wsPiv[0]) + wsPivY.append(wsPiv[1]) + wsPivZ.append(wsPiv[2]) + lsPiv.append(mc.xform(ele, q=1, rp=1)) + tr.append(mc.xform(ele, q=1, t=1)) + wsPivX = sum(wsPivX) / len(wsPivX) + wsPivY = sum(wsPivY) / len(wsPivY) + wsPivZ = sum(wsPivZ) / len(wsPivZ) + + mc.xform(sourceCurves, piv=[wsPivX, wsPivY, wsPivZ], wd=1, ws=1) + + mc.move(0, 0, 0, sourceCurves, rpr=1) + findGeo = [] + for part in sourceCurves: + findGeo.append(selectPart(2, True, part)[0]) + + if len(findGeo) > 1: + unite = mc.polyUnite(findGeo, ch=1, muv=1, n='warpCard#') + mc.rename(unite[1], 'gsUniteNode#') + self.geo = unite[0] + else: + tempNode = mc.createNode('mesh') + unite = mc.polyUnite([findGeo[0], tempNode], ch=1, muv=1, n='warpCard#') + mc.rename(unite[1], 'gsUniteNode#') + self.geo = unite[0] + mc.delete(mc.listRelatives(mc.listRelatives(tempNode, p=1, pa=1), p=1, pa=1)) + + shader = utils.getShader(self.geo) + for key in shader: + mc.sets(shader[key], e=1, fe=key, nw=1) + mc.select(self.geo, r=1) + else: + self.pathCurve = self.pathCurve[0] + self.geo = self.geo[0] + mc.makeIdentity(self.geo, a=1, t=1, r=1, s=1, n=0, pn=1) + mc.makeIdentity(self.pathCurve, a=1, t=1, r=1, s=1, n=0, pn=1) + mc.makeIdentity(self.pathCurve, a=0, t=1, r=1, s=1, n=0, pn=1) + + # Cleaning previous attrs (Replace with remembering attrs) + prevAttrs = attributes.getAttr(self.pathCurve) + graphAttrs = {} + for attr in attributes.graphAttributes: + if mc.attributeQuery(attr, n=self.pathCurve, ex=1): + graphAttrs[attr] = mc.getAttr(self.pathCurve + '.' + attr) + for attr in attributes.attrList | attributes.checkBoxes | attributes.uvAttr: + if mc.attributeQuery(attr, n=self.pathCurve, ex=1): + mc.deleteAttr(self.pathCurve + '.' + attr) + + # Main warp + if not isCurveWarp: + mc.select(self.pathCurve, self.geo, r=1) + if MAYA_VER <= 2017: + mel.eval('MoveTool;') + mel.eval('BakeCustomPivot;') + attributes.deleteAttr(self.pathCurve) + self.pathCurve = mc.rename(self.pathCurve, 'pathCurve#') + self.geo = mc.rename(self.geo, 'geoCard#') + + self.polyNormalNode = mc.polyNormal(self.geo, ch=1, unm=0, nm=1)[0] + + self.curveWarp = mc.deformer(ignoreSelected=1, type='curveWarp', n='curveWarp#')[0] + mc.deformer(self.curveWarp, e=1, g=self.geo) + mc.connectAttr(self.pathCurve + '.worldSpace[0]', self.curveWarp + '.inputCurve') + + mc.setAttr(self.geo + '.inheritsTransform', 0) + if MAYA_VER >= 2018: + mc.setAttr(self.curveWarp + '.samplingAccuracy', 0.333) + mc.addAttr(self.pathCurve, ln='samplingAccuracy', dv=0.333, smx=2, at='double', min=0.001, k=1) + mc.connectAttr(self.pathCurve + '.samplingAccuracy', self.curveWarp + '.samplingAccuracy') + mc.setAttr(self.curveWarp + '.twistRotation', 720) + + # Optionally flip axis and normals and change orientation + axisFlipDefault = 0 + orientationDefault = 0 + reverseNormalsDefault = 1 + if getOption('bindFlipUVs'): + axisFlipDefault = 1 + orientationDefault = 90 + reverseNormalsDefault = 0 + + mc.addAttr(self.pathCurve, ln='Axis', dv=0, at='enum', en="Auto:X:Y:Z:", k=1) + mc.addAttr(self.pathCurve, ln='AxisFlip', dv=axisFlipDefault, at='bool', k=1) + mc.addAttr(self.pathCurve, ln='Orientation', smn=-360, dv=orientationDefault, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='Width', dv=2, smx=5, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln='LengthLock', at='enum', dv=0, en="Locked:Unlocked:", k=1) + mc.addAttr(self.pathCurve, ln='Length', dv=1, smx=200, at='double', min=0.001, k=1) + mc.addAttr(self.pathCurve, ln='Offset', dv=0, smx=1, smn=-1, at='double', min=-30, max=30, k=1) + mc.addAttr(self.pathCurve, ln="reverseNormals", dv=reverseNormalsDefault, en="reverse:off:", at="enum", k=1) + + mc.connectAttr(self.pathCurve + '.AxisFlip', self.curveWarp + '.flipAxis') + mc.connectAttr(self.pathCurve + '.Width', self.curveWarp + '.maxScale') + mc.connectAttr(self.pathCurve + '.LengthLock', self.curveWarp + '.keepLength') + mc.connectAttr(self.pathCurve + '.Length', self.curveWarp + '.lengthScale') + mc.connectAttr(self.pathCurve + '.Offset', self.curveWarp + '.offset') + mc.connectAttr(self.pathCurve + ".reverseNormals", self.polyNormalNode + ".normalMode", f=1) + + # Twist Magnitude + + mc.addAttr(self.pathCurve, ln='Magnitude', dv=0.5, at='double', h=1) + expr = ''' + {0}.rotation = 360 * (1 - {1}.Magnitude) + {1}.Orientation; + {0}.twistRotation = 720 * {1}.Magnitude; + {0}.alignmentMode = {1}.Axis + 1; + '''.format(self.curveWarp, self.pathCurve) + self.magnExpr = mc.expression(ae=0, s=expr, n='twistMagnitudeCalc#') + + self.addRefine(True) + self.addAutoRefine() + + # Set prev attributes + if WIDGETS['keepCurveAttributes'].isChecked(): + for attr in graphAttrs: + values = graphAttrs[attr] + if attr == 'profileCurve': + updateLattice(values, self.pathCurve) + else: + rebuildCurve = mc.listConnections(self.pathCurve + '.curveSmooth')[0] + warp = mc.listConnections(rebuildCurve + '.outputCurve')[0] + graphValues = utils.fromStringToDouble2(values) + utils.setDouble2Attr(warp, attr, graphValues) + attributes.setAttr(self.pathCurve, prevAttrs) + + self.setCurveThickness() + self.group(grpName, True) + + if isCurveWarp: + origCurves = mc.group(origGroups, n='origCurves#') + origCurvesGrp = mc.parent(origCurves, self.hairCardGrp) + mc.setAttr(origCurvesGrp[0] + '.visibility', 0) + + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + + mc.select(self.hairCardGrp + '|' + self.pathCurve, r=1) + resetCurvePivotPoint(self.hairCardGrp + '|' + self.pathCurve) + + def unbind(self): + self.initialize() + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, o=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one compatible curve (Bound/Warp curves or geometry)') + return + previousCurves = [] + newSel = [] + for obj in sel: + initialGroup = selectPart(0, True, obj) + origGeometry = selectPart(2, True, obj) + if not origGeometry: + continue + shader = utils.getShader(origGeometry) + topGroup = mc.listRelatives(initialGroup, p=1, pa=1) + origCurvesGrp = selectPart(3, True, obj) + if not origCurvesGrp: # If orig objects are geometry + if not mc.attributeQuery('Axis', n=obj, ex=1): + LOGGER.info('%s is not a bound group. Skipping.' % obj) + continue + mc.editDisplayLayerMembers("defaultLayer", origGeometry, nr=1) + newGeo = [] + if topGroup: + newGeo = mc.parent(origGeometry, topGroup) + previousCurves.append(mc.parent(mc.duplicate(obj), topGroup)) + else: + newGeo = mc.parent(origGeometry, w=1) + previousCurves.append(mc.parent(mc.duplicate(obj), w=1)) + mc.delete(initialGroup) + mc.delete(newGeo, ch=1) + newSel.append(newGeo[0]) + else: # If orig objects are curves + origGroups = mc.listRelatives(origCurvesGrp, c=1, pa=1) + newCards = [] + if not origGroups: + LOGGER.info('%s is not a bound card/geo. Skipping' % obj) + continue + if topGroup: + newCards = mc.parent(origGroups, topGroup) + previousCurves.append(mc.parent(mc.duplicate(obj), topGroup)) + else: + newCards = mc.parent(origGroups, w=1) + previousCurves.append(mc.parent(mc.duplicate(obj), w=1)) + mc.delete(initialGroup) + for card in newCards: + geo = selectPart(2, True, card) + nextNode = mc.listRelatives(geo, c=1, pa=1) + i = 0 + while mc.nodeType(nextNode) != 'mesh': + i += 1 + if i >= 100: + break + nextNode = mc.listRelatives(nextNode, c=1, pa=1) + mc.setAttr(nextNode[0] + '.intermediateObject', 0) + transformNode = mc.listRelatives(nextNode, p=1, pa=1) + transformNode = mc.parent(transformNode, card) + connections = mc.listConnections(geo, d=0, s=1, p=1) + for connection in connections: + attr = connection.split('.')[1] + target = '%s.%s' % (transformNode[0], attr) + if mc.attributeQuery(attr, n=transformNode[0], ex=1): + mc.connectAttr(connection, target, f=1) + # NOTE: Rerouting connections manually to avoid node deletions (Autodesk bug in 2022, 2023) + geoShape = mc.listRelatives(geo, c=1, pa=1) + if geoShape: + nurbsTesselate = mc.listConnections(geoShape[0] + '.inMesh', p=1) + destConnections1 = mc.listConnections(geoShape[0] + '.worldMesh[0]', p=1) + destConnections2 = mc.listConnections(geoShape[0] + '.outMesh', p=1) + if nurbsTesselate and destConnections1 and destConnections2: + for destCon in destConnections1 + destConnections2: + mc.connectAttr(nurbsTesselate[0], destCon, f=1) + mc.delete(geo) + transformNode = mc.rename(transformNode, geo) + mc.sets(transformNode, forceElement=list(shader.keys())[0]) + mc.setAttr(transformNode + '.v', 1) + curve = selectPart(1, True, card) + layerID = getCurveLayer(curve[0]) + curveAddToLayer(layerID, inputCurves=curve) + if getOption('bindFlipUVs'): + if mc.attributeQuery('flipUV', n=curve[0], ex=1): + mc.setAttr(curve[0] + '.flipUV', not mc.getAttr(curve[0] + '.flipUV')) + newSel.append(curve[0]) + for curve in previousCurves: + mc.editDisplayLayerMembers("defaultLayer", curve, nr=1) + toggleColor.resetSingleCurve(curve) + mc.rename(curve, 'unboundCurve#') + mc.select(newSel) + + ### Curve Creation Modules ### + + def addRefine(self, output=None): + inst = None + if output: + output = self.curveWarp + '.inputCurve' + inst = self.pathCurve + else: + output = self.extrude + '.path' + inst = self.pathInst + + self.rebuildCurve = mc.createNode('rebuildCurve') + mc.setAttr(self.rebuildCurve + '.keepRange', 2) + mc.addAttr(self.pathCurve, ln='curveRefine', smn=-1, dv=self.refine, at='long', smx=300, k=1) + mc.addAttr(self.pathCurve, ln='curveSmooth', dv=0, smx=10, at='double', min=-0, k=1) + mc.connectAttr(inst + '.worldSpace[0]', self.rebuildCurve + '.inputCurve', f=1) + mc.connectAttr(self.rebuildCurve + '.outputCurve', output, f=1) + mc.connectAttr(self.pathCurve + '.curveRefine', self.rebuildCurve + '.spans', f=1) + mc.connectAttr(self.pathCurve + '.curveSmooth', self.rebuildCurve + '.smooth', f=1) + + def addPolyNormal(self): + self.polyNormalNode = mc.polyNormal(self.hairCard, ch=1, nm=2)[0] + self.polySoftEdge = mc.polySoftEdge(self.hairCard, ch=1, a=180)[0] + mc.addAttr(self.pathCurve, ln="reverseNormals", dv=1, en="reverse:off:", at="enum", k=1) + mc.addAttr(self.pathCurve, ln="surfaceNormals", dv=180, smx=180, at='double', min=-0, k=1) + mc.connectAttr(self.pathCurve + ".reverseNormals", self.polyNormalNode + ".normalMode", f=1) + mc.connectAttr(self.pathCurve + ".surfaceNormals", self.polySoftEdge + ".angle", f=1) + + def addUVs(self): + self.polyFlipUV = mc.polyFlipUV(self.hairCard, ch=True, up=True, pu=0.5, pv=0.5)[0] + self.polyMoveUV_root = mc.polyMoveUV(self.hairCard, ch=True, pvu=0.5, pvv=0)[0] + self.polyMoveUV_mid = mc.polyMoveUV(self.hairCard, ch=True, pvu=0.5, pvv=0.5)[0] + + mc.setAttr(self.polyFlipUV + '.usePivot', 1) + mc.setAttr(self.polyFlipUV + '.pivotU', 0.5) + mc.setAttr(self.polyFlipUV + '.pivotV', 0.5) + + mc.addAttr(self.pathCurve, ln="flipUV", dv=1, en="flip:off:", at="enum", k=1) + mc.addAttr(self.pathCurve, ln='moveU', smn=-1, dv=0, at='double', smx=1, k=1) + mc.addAttr(self.pathCurve, ln='moveV', smn=-1, dv=0, at='double', smx=1, k=1) + mc.addAttr(self.pathCurve, ln='rotateUV', smn=-360, dv=0, at='double', smx=360, k=1) + mc.addAttr(self.pathCurve, ln='scaleU', smn=0, dv=1, at='double', smx=5, k=1) + mc.addAttr(self.pathCurve, ln='scaleV', smn=0, dv=1, at='double', smx=5, k=1) + + uv_rotate = ''' + {1}.pivotU = ({0}.translateU + 0.5); + {1}.pivotV = ({0}.translateV + 0.5); + '''.format(self.polyMoveUV_root, self.polyMoveUV_mid) + self.uvExpr = mc.expression(ae=0, n='UV_Rotation#', s=uv_rotate) + + mc.connectAttr(self.pathCurve + '.flipUV', self.polyFlipUV + '.nodeState', f=1) + mc.connectAttr(self.pathCurve + '.moveU', self.polyMoveUV_root + '.translateU', f=1) + mc.connectAttr(self.pathCurve + '.moveV', self.polyMoveUV_root + '.translateV', f=1) + mc.connectAttr(self.pathCurve + '.rotateUV', self.polyMoveUV_mid + '.rotationAngle', f=1) + mc.connectAttr(self.pathCurve + '.scaleU', self.polyMoveUV_root + '.scaleU', f=1) + mc.connectAttr(self.pathCurve + '.scaleV', self.polyMoveUV_root + '.scaleV', f=1) + + def addSolidify(self): + self.solidifyNode = mc.createNode('polyExtrudeFace') + self.solidifyChoice = mc.createNode('choice') + mc.addAttr(self.pathCurve, ln='solidify', dv=0, at='bool', k=1) + mc.addAttr(self.pathCurve, ln='solidifyThickness', smn=0, dv=.25, at='double', smx=10, k=1) + mc.addAttr(self.pathCurve, ln='solidifyDivisions', smn=0, dv=0, at='long', smx=10, k=1) + mc.addAttr(self.pathCurve, ln='solidifyScaleX', smn=-10, dv=1, at='double', smx=10, k=1) + mc.addAttr(self.pathCurve, ln='solidifyScaleY', smn=-10, dv=1, at='double', smx=10, k=1) + mc.addAttr(self.pathCurve, ln='solidifyOffset', smn=-10, dv=0, at='double', smx=10, k=1) + mc.addAttr(self.pathCurve, ln='solidifyNormals', smn=0, dv=30, at='double', smx=180, k=1) + mc.setAttr(self.solidifyNode + '.inputComponents', 1, 'f[*]', type='componentList') + mc.connectAttr(self.hairCard + '.worldMatrix[0]', self.solidifyNode + '.manipMatrix', f=1) + mc.connectAttr(self.polyMoveUV_mid + '.output', self.solidifyChoice + '.input[0]', f=1) + mc.connectAttr(self.polyMoveUV_mid + '.output', self.solidifyNode + '.inputPolymesh', f=1) + mc.connectAttr(self.solidifyNode + '.output', self.solidifyChoice + '.input[1]', f=1) + mc.connectAttr(self.solidifyChoice + '.output', self.hairCard + '.inMesh', f=1) + mc.connectAttr(self.pathCurve + '.solidify', self.solidifyChoice + '.selector', f=1) + mc.connectAttr(self.pathCurve + '.solidifyThickness', self.solidifyNode + '.localTranslateZ', f=1) + mc.connectAttr(self.pathCurve + '.solidifyDivisions', self.solidifyNode + '.divisions', f=1) + mc.connectAttr(self.pathCurve + '.solidifyScaleX', self.solidifyNode + '.localScaleX', f=1) + mc.connectAttr(self.pathCurve + '.solidifyScaleY', self.solidifyNode + '.localScaleY', f=1) + mc.connectAttr(self.pathCurve + '.solidifyOffset', self.solidifyNode + '.offset', f=1) + mc.connectAttr(self.pathCurve + '.solidifyNormals', self.solidifyNode + '.smoothingAngle', f=1) + + def addMessage(self): + if not mc.attributeQuery('gsmessage', n=self.pathCurve, ex=1): + mc.addAttr(self.pathCurve, ln='gsmessage', at='message', k=0) + + def hideNodes(self): + nodes = [ + self.nurbsTesselate, + self.curveWarp, + self.polyFlipUV, + self.polyMoveUV_mid, + self.polyMoveUV_tip, + self.polyMoveUV_root, + self.solidifyNode, + self.solidifyChoice, + self.extrude, + self.magnExpr, + self.scaleExpr, + self.uvExpr, + self.rebuildCurve, + self.polyNormalNode, + self.polySoftEdge, + self.lattice, + self.twist, + self.curveInfoNode, + self.dynamicDivisionsCalculate, + self.autoRefineCalculate, + ] + assetList = [] + for node in nodes: + if node: + if isinstance(node, list): + for n in node: + if mc.objExists(n): + assetList.append(n) + mc.setAttr(n + '.isHistoricallyInteresting', 0) + else: + if mc.objExists(node): + assetList.append(node) + mc.setAttr(node + '.isHistoricallyInteresting', 0) + + def setCurveThickness(self): + shape = mc.ls(self.pathCurve, dag=1, s=1, l=1)[0] + mc.setAttr(shape + '.lineWidth', self.globalThickness) + + def addScaleFactor(self): + # Delete the attribute if saving is not checked + if not WIDGETS['keepCurveAttributes'].isChecked(): + if mc.attributeQuery('scaleFactor', n=self.pathCurve, ex=1): + mc.deleteAttr(self.pathCurve + '.scaleFactor') + # Add the attribute + if not mc.attributeQuery('scaleFactor', n=self.pathCurve, ex=1): + mc.addAttr(self.pathCurve, ln='scaleFactor', at='double', dv=self.sf) + mc.setAttr(self.pathCurve + '.scaleFactor', self.sf) + + def addDynamicDivisions(self, curve=None, tesselateNode=None): + if not curve: + curve = self.pathCurve + if not tesselateNode: + tesselateNode = self.nurbsTesselate + curveShape = mc.listRelatives(curve, c=1, typ='nurbsCurve') + if not curveShape: + return + curveShape = curveShape[0] + self.curveInfoNode = mc.createNode('curveInfo', n='dynamicDivisionsInfo#', ss=1) + mc.connectAttr(curveShape + '.worldSpace[0]', self.curveInfoNode + '.inputCurve', f=1) + if not mc.attributeQuery('dynamicDivisions', n=curve, ex=1): + mc.addAttr(curve, ln='dynamicDivisions', dv=0, at='bool', k=1) + mc.disconnectAttr(curve + '.lengthDivisions', tesselateNode + '.vNumber') + + if not mc.attributeQuery('scaleFactor', n=curve, ex=1): + mc.addAttr(curve, ln='scaleFactor', at='double', dv=self.sf) + + if not mc.attributeQuery('dynamicDivMult', n=curve, ex=1): # NOTE: In case we need this in the future + mc.addAttr(curve, ln='dynamicDivMult', at='double', dv=1.0, min=0.01, max=1000, k=1) + + expr = ''' + if ({2}.dynamicDivisions) + {{ + {0}.vNumber = {1}.arcLength * {2}.lengthDivisions * 0.05 * {2}.dynamicDivMult / {2}.scaleFactor; + }} + else + {{ + {0}.vNumber = {2}.lengthDivisions; + }} + '''.format(tesselateNode, self.curveInfoNode, curve) + self.dynamicDivisionsCalculate = mc.expression(ae=0, s=expr, n='dynamicDivisionsCalculate#') + + def addAutoRefine(self, curve=None, rebuildCurve=None): + """Add automatic rebuild value calculation to existing curveRebuild node""" + useAutoRefine = True + if not curve: + curve = self.pathCurve + useAutoRefine = mc.optionVar(q="GSCT_useAutoRefineOnNewCurves") + if not rebuildCurve: + rebuildCurve = self.rebuildCurve + useAutoRefine = mc.optionVar(q="GSCT_useAutoRefineOnNewCurves") + + if not mc.attributeQuery('autoRefine', n=curve, ex=1): + mc.addAttr(curve, ln='autoRefine', dv=useAutoRefine, at='bool', k=1) + + # Check if curveRefine connection is correct + curveRefineTarget = mc.listConnections(curve + '.curveRefine') + for node in curveRefineTarget: + if mc.nodeType(node) == "expression": + continue + else: + mc.disconnectAttr(curve + '.curveRefine', node + '.spans') + + expr = ''' + if ({0}.autoRefine) + {{ + {1}.keepControlPoints = 1; + }} + else + {{ + {1}.keepControlPoints = 0; + {1}.spans = {0}.curveRefine; + }} + '''.format(curve, rebuildCurve) + self.autoRefineCalculate = mc.expression(ae=0, s=expr, n='autoRebuildCalculate#') + return self.autoRefineCalculate + + def clearTweakNode(self): # TODO: Check if this fix is needed + pass + # Possible fix for older Maya versions tweak node issue + # try: + # tweakNode = mc.listConnections(self.hairCard + '.tweakLocation') + # if tweakNode: + # mc.delete(tweakNode) + # except: + # pass + + def group(self, grpName, fake=False): + constructionGrp = None + if fake: + constructionGrp = mc.createNode('transform', n='instances#') + self.hairCard = self.geo + elif self.lattice: + constructionGrp = mc.group(self.pathInst, self.profileInst, self.lattice, self.twist, n='instances#') + else: + constructionGrp = mc.group(self.pathInst, self.profileInst, n='instances#') + mc.setAttr(constructionGrp + '.inheritsTransform', 0) + mc.setAttr(constructionGrp + '.tx', lock=True) + mc.setAttr(constructionGrp + '.ty', lock=True) + mc.setAttr(constructionGrp + '.tz', lock=True) + mc.setAttr(constructionGrp + '.rx', lock=True) + mc.setAttr(constructionGrp + '.ry', lock=True) + mc.setAttr(constructionGrp + '.rz', lock=True) + mc.setAttr(constructionGrp + '.sx', lock=True) + mc.setAttr(constructionGrp + '.sy', lock=True) + mc.setAttr(constructionGrp + '.sz', lock=True) + + # Layers + hairGrp = self.currentLayerInd() + curveGrp, geoGrp, instGrp = utils.getFormattedLayerNames(self.collectionID, hairGrp) + + if mc.objExists(instGrp) != 1: + utils.createNewDisplayLayer(name=instGrp, objects=constructionGrp) + mc.setAttr(instGrp + '.displayType', 2) + mc.setAttr(instGrp + '.visibility', 0) + else: + mc.editDisplayLayerMembers((instGrp), constructionGrp, nr=1) + mc.setAttr(instGrp + '.displayType', 2) + mc.setAttr(instGrp + '.visibility', 0) + + if mc.objExists(geoGrp) != 1: + utils.createNewDisplayLayer(name=geoGrp, objects=self.hairCard) + mc.setAttr(geoGrp + '.displayType', 2) + else: + mc.editDisplayLayerMembers(geoGrp, self.hairCard, nr=1) + + if mc.objExists(curveGrp) != 1: + utils.createNewDisplayLayer(name=curveGrp, objects=self.pathCurve) + else: + mc.editDisplayLayerMembers(curveGrp, self.pathCurve, nr=1) + + layerCollections.updateDefaultLayerNode() + layerCollections.updateCollectionNames() + + # Final Group Lock + self.hairCardGrp = str(mc.group(self.pathCurve, constructionGrp, self.hairCard, n=grpName)) + mc.setAttr(self.hairCardGrp + '|' + self.hairCard + '.inheritsTransform', 1) # TODO: Why do we need this? + mc.setAttr(self.hairCardGrp + '.inheritsTransform', 0) # TODO: Check if this breaks anything + mc.setAttr(self.hairCardGrp + '.tx', lock=True) + mc.setAttr(self.hairCardGrp + '.ty', lock=True) + mc.setAttr(self.hairCardGrp + '.tz', lock=True) + mc.setAttr(self.hairCardGrp + '.rx', lock=True) + mc.setAttr(self.hairCardGrp + '.ry', lock=True) + mc.setAttr(self.hairCardGrp + '.rz', lock=True) + mc.setAttr(self.hairCardGrp + '.sx', lock=True) + mc.setAttr(self.hairCardGrp + '.sy', lock=True) + mc.setAttr(self.hairCardGrp + '.sz', lock=True) + + +create = Create("create") + + +class Sliders: + + def __init__(self, name): + self.name = name + self.icons = utils.getFolder.icons() + self.timer = utils.Timer() + self.widthLock = False + self.rebuildDupTrans = list() + self.rebuildDupShape = list() + self.rebuildCurveNode = list() + self.rebuildTargetNode = list() + self.lastCV = list() + self.curveRepeat = int() + self.curveTrigger = int() + self.sliderCheck = int() + self.curveCVsName = list() + self.curveCVs = list() + self.curveRotate = list() + self.curveOrientation = list() + self.curveTwist = list() + self.curveWidth = list() + self.curveWidthX = list() + self.curveWidthZ = list() + self.curveTaper = list() + self.curveProfile = list() + self.curveRandDragSelList = [] + + def release(self, *_): # Close undo chunk on release + mc.undoInfo(cck=1) + self.sliderCheck = 0 + + def selectCVSlider(self, sliderValue): # Select CV Slider + if self.sliderCheck == 0: + mc.undoInfo(ock=1, cn='gsCVSlider') + self.sliderCheck = 1 + sel = mc.filterExpand(mc.ls(sl=1, o=1, fl=1, dag=1, s=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, o=1, fl=1, dag=1, s=1), sm=9) + if not sel: + return + modifier = utils.getMod() + selectionList = [] + for curve in sel: + shape = mc.listRelatives(curve, c=1, pa=1, ni=1, s=1, typ='nurbsCurve') + if not shape: + continue + shape = shape[0] + numCVs = mc.getAttr(shape + '.controlPoints', s=1) + position = math.floor(numCVs * sliderValue) + selectionList.append("{}.cv[{}]".format(shape, position)) + if modifier == "Ctrl": + return + if modifier == "Alt" or modifier == "Shift+Alt": + if len(mc.ls(sl=1, fl=1)) <= len(sel): + return + mc.select(selectionList, d=1) + elif modifier == "Shift": + mc.select(selectionList, add=1) + else: + mc.select(selectionList, r=1) + + def rebuildSliderDrag(self, *_): # Rebuild curve drag command # BUG: Rebuilding to small values can break advanced visibility curve drawing + if self.sliderCheck == 0: + mc.undoInfo(ock=1, cn='gsRebuildSlider') + self.sliderCheck = 1 + slider = mc.intSliderGrp('gsRebuildSlider', q=1, v=1) + sel = mc.filterExpand(mc.ls(sl=1, o=1, fl=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, o=1, fl=1), sm=9) + if not sel: + return 0 + j = int() + sha = mc.ls(sel, dag=1, s=1) + for obj in sha: + inputPlug = mc.listConnections(obj + '.create', d=0, s=1, p=1) + if inputPlug: # Rebuild Algorithm if rebuildCurve node is detected + if len(self.rebuildDupTrans) <= j or not mc.objExists(self.rebuildDupTrans[j]): + target = mc.listConnections(obj + '.worldSpace[1]', d=1, s=0, p=1) + nurbsCurve = mc.createNode('nurbsCurve', n='gsTempCurve') + mc.setAttr(nurbsCurve + '.dispCV', 1) + nurbsCurveTransform = mc.listRelatives(nurbsCurve, p=1, pa=1) + mc.setAttr(nurbsCurveTransform[0] + '.hiddenInOutliner', 1) + mc.matchTransform(nurbsCurveTransform[0], mc.listRelatives(obj, p=1, pa=1)[0]) + rebuildCurve = mc.createNode('rebuildCurve') + mc.connectAttr(obj + ".worldSpace[1]", rebuildCurve + ".inputCurve", f=1) + mc.connectAttr(rebuildCurve + ".outputCurve", nurbsCurve + ".create", f=1) + mc.connectAttr(nurbsCurve + '.local', target[0], f=1) + utils.addAtIndex(self.rebuildDupTrans, j, nurbsCurveTransform[0]) + utils.addAtIndex(self.rebuildDupShape, j, nurbsCurve) + utils.addAtIndex(self.rebuildCurveNode, j, rebuildCurve) + utils.addAtIndex(self.rebuildTargetNode, j, target[0]) + j += 1 + else: # Rebuild Algorithm if rebuildCurve node is not detected + if len(self.rebuildDupTrans) <= j or not mc.objExists(self.rebuildDupTrans[j]): + tra = mc.duplicate(obj, rc=1) + shp = mc.listRelatives(tra[0], c=1, pa=1) + rebuildCurve = mc.createNode('rebuildCurve') + mc.setAttr(rebuildCurve + '.keepRange', 1) + mc.setAttr(tra[0] + '.hiddenInOutliner', 1) + mc.setAttr(rebuildCurve + '.spans', (mc.getAttr(obj + '.spans'))) + mc.setAttr(rebuildCurve + '.degree', (mc.getAttr(obj + '.degree'))) + mc.connectAttr(shp[0] + '.worldSpace', rebuildCurve + '.inputCurve', f=1) + mc.connectAttr(rebuildCurve + '.outputCurve', obj + '.create', f=1) + utils.addAtIndex(self.rebuildDupTrans, j, tra[0]) + utils.addAtIndex(self.rebuildDupShape, j, shp[0]) + utils.addAtIndex(self.rebuildCurveNode, j, rebuildCurve) + j += 1 + mc.setAttr(obj + '.dispCV', 1) + + for node in self.rebuildCurveNode: + mc.setAttr(node + '.spans', slider) + # TODO: Add in v1.3 with different refine modes and auto refine values + # for curve in sel: + # if mc.attributeQuery('curveRefine', n=curve, ex=1): + # mc.setAttr(curve + '.curveRefine', 20 if slider <= 20 else 0) + mc.select(sel, r=1) + mc.headsUpMessage('[%s]' % slider, s=1) + + def rebuildSliderRelease(self, *_): # Rebuild curve slider release command + if self.sliderCheck == 0: + mc.undoInfo(ock=1, cn='gsRebuildSlider') + self.sliderCheck = 1 + selTemp = mc.ls(sl=1, o=1, fl=1, dag=1, s=1) + sel = list() + for obj in selTemp: + if utils.attrExists(obj, 'spans'): + sel.append(obj) + if len(sel) > 0: + slider = mc.intSliderGrp('gsRebuildSlider', q=1, v=1) + if len(self.rebuildDupTrans) == 0: + for i in range(len(sel)): + inputPlug = mc.listConnections(sel[i] + '.create', d=0, s=1, p=1) + rebuildCurve = list() + if not inputPlug: + rebuildCurve = mc.rebuildCurve(sel[i], s=slider, ch=0, kr=1) + else: + rebuildCurve = mc.rebuildCurve(sel[i], s=slider, ch=1, kr=1) + newName = mc.rename(rebuildCurve[1], 'gsRebuildCurveNode#') + hist = mc.listHistory(newName) + for ele in hist: + if 'gsRebuildCurveNode' in ele and ele != newName: + mc.delete(ele) + mc.rename(newName, 'gsRebuildCurveNode1') + mc.headsUpMessage('[%s]' % slider, s=1) + else: + try: + mc.delete(self.rebuildDupTrans) + except BaseException: + pass + try: + mc.delete(self.rebuildDupShape) + except BaseException: + pass + self.rebuildDupTrans *= 0 + self.rebuildDupShape *= 0 + self.rebuildCurveNode *= 0 + self.rebuildTargetNode *= 0 + + for obj in sel: + try: + mc.setAttr(obj + '.dispCV', 0) + except BaseException: + pass + mc.select(mc.listRelatives(sel, p=1, pa=1), r=1) + + def rebuildButtonClicked(self): + self.rebuildSliderDrag() + self.rebuildSliderRelease() + self.release() + + def randSliderDrag(self, sldr, *_): + """Randomize Curve sliders drag""" + # BUG: Fix width and taper slider to be changed based on the original values, not on some arbitrary ones + if self.sliderCheck == 0: + mc.undoInfo(ock=1, cn='curveRandSlider') + self.sliderCheck = 1 + sel = mc.filterExpand(mc.ls(sl=1, o=1, fl=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, o=1, fl=1), sm=9) + + if not sel: + return + + slid = list() + vect = str() + + for i in range(8): + slid.append(WIDGETS['curveRandomizeSlider' + str(i)].isChecked()) + + if slid[0] == 1 and (sldr == 0 or sldr == -1): + if self.curveRepeat == 1: + for i in range(len(self.curveCVsName)): + vect = self.curveCVs[i] + mc.move(vect[0], vect[1], vect[2], self.curveCVsName[i], a=1) + lockFirstCV = WIDGETS['gsLockFirstCV'].isChecked() + lockLastCV = WIDGETS['gsLockLastCV'].isChecked() + slider = mc.floatSliderGrp('gsCurveCVsRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveCVsRandMulti', q=1, v=1) + value = slider * mult + for obj in sel: + ind = mc.getAttr(obj + '.cp', mi=1) + CVs = list() + for i in range(len(ind)): + CVs.append(str(obj) + '.cv[' + str(i) + ']') + if self.curveTrigger == 0: + pp = mc.pointPosition(obj + '.cp[' + str(i) + ']') + self.curveCVsName.append(CVs[i]) + self.curveCVs.append([pp[0], pp[1], pp[2]]) + for i in range(len(CVs)): + if (i == 0 or i == 1) and lockFirstCV == 1: + continue + if i == len(CVs) - 1 and lockLastCV == 1: + continue + randX = random.uniform(value * -1, value) + randY = random.uniform(value * -1, value) + randZ = random.uniform(value * -1, value) + if not WIDGETS['gsRandAxisX'].isChecked(): + randX = 0 + if not WIDGETS['gsRandAxisY'].isChecked(): + randY = 0 + if not WIDGETS['gsRandAxisZ'].isChecked(): + randZ = 0 + mc.move(randX, randY, randZ, CVs[i], r=1, os=1, wd=1) + + if slid[1] == 1 and (sldr == 1 or sldr == -1): + slider = mc.floatSliderGrp('gsCurveRotationRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveRotationRandMulti', q=1, v=1) + value = slider * mult + for i in range(len(sel)): + if self.curveTrigger == 0: + rotX = mc.getAttr(sel[i] + '.rx') + rotY = mc.getAttr(sel[i] + '.ry') + rotZ = mc.getAttr(sel[i] + '.rz') + self.curveRotate.append([rotX, rotY, rotZ]) + if self.curveRepeat == 1: + rotate = self.curveRotate[i] + mc.setAttr(sel[i] + '.rx', rotate[0]) + mc.setAttr(sel[i] + '.ry', rotate[1]) + mc.setAttr(sel[i] + '.rz', rotate[2]) + for obj in sel: + randX = random.uniform(value * -1, value) + randY = random.uniform(value * -1, value) + randZ = random.uniform(value * -1, value) + if not WIDGETS['gsRandRotateAxisX'].isChecked(): + randX = 0 + if not WIDGETS['gsRandRotateAxisY'].isChecked(): + randY = 0 + if not WIDGETS['gsRandRotateAxisZ'].isChecked(): + randZ = 0 + mc.rotate(randX, randY, randZ, obj, r=1, os=1, fo=1) + + if slid[2] == 1 and (sldr == 2 or sldr == -1): + slider = mc.floatSliderGrp('gsCurveOrientationRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveOrientationRandMulti', q=1, v=1) + value = slider * mult + for i in range(len(sel)): + if self.curveTrigger == 0: + self.curveOrientation.append(mc.getAttr(sel[i] + '.Orientation')) + if self.curveRepeat == 1: + mc.setAttr(sel[i] + '.Orientation', self.curveOrientation[i]) + for obj in sel: + rand = random.uniform(value * -1, value) + mc.setAttr(obj + '.Orientation', rand) + + if slid[3] == 1 and (sldr == 3 or sldr == -1): + slider = mc.floatSliderGrp('gsCurveTwistRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveTwistRandMulti', q=1, v=1) + value = slider * mult + for i in range(len(sel)): + if self.curveTrigger == 0: + self.curveTwist.append(mc.getAttr(sel[i] + '.Twist')) + if self.curveRepeat == 1: + mc.setAttr(sel[i] + '.Twist', self.curveTwist[i]) + for obj in sel: + rand = random.uniform(value * -1, value) + mc.setAttr(obj + '.Twist', rand) + + if slid[4] == 1 and (sldr == 4 or sldr == -1): + slider = mc.floatSliderGrp('gsCurveWidthRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveWidthRandMulti', q=1, v=1) + value = slider * mult + for i in range(len(sel)): + if self.curveTrigger == 0: + if mc.attributeQuery('Width', n=sel[i], ex=1): + self.curveWidth.append(mc.getAttr(sel[i] + '.Width')) + else: + self.curveWidthX.append(mc.getAttr(sel[i] + '.WidthX')) + self.curveWidthZ.append(mc.getAttr(sel[i] + '.WidthZ')) + if self.curveRepeat == 1: + if mc.attributeQuery('Width', n=sel[i], ex=1): + mc.setAttr(sel[i] + '.Width', self.curveWidth[i]) + else: + mc.setAttr(sel[i] + '.WidthX', self.curveWidthX[i]) + mc.setAttr(sel[i] + '.WidthZ', self.curveWidthZ[i]) + rand = random.uniform(0.001, value) + if mc.attributeQuery('Width', n=sel[i], ex=1): + mc.setAttr(sel[i] + '.Width', rand) + else: + mc.setAttr(sel[i] + '.WidthX', rand) + if not WIDGETS['gsWidthCheckBoxUniform'].isChecked(): + rand = random.uniform(0.001, value) + mc.setAttr(sel[i] + '.WidthZ', rand) + + if slid[5] == 1 and (sldr == 5 or sldr == -1): + slider = mc.floatSliderGrp('gsCurveTaperRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveTaperRandMulti', q=1, v=1) + value = slider * mult + for i in range(len(sel)): + if self.curveTrigger == 0: + self.curveTaper.append(mc.getAttr(sel[i] + '.Taper')) + if self.curveRepeat == 1: + mc.setAttr(sel[i] + '.Taper', self.curveTaper[i]) + rand = random.uniform(0, value) + mc.setAttr(sel[i] + '.Taper', rand) + + if slid[6] == 1 and (sldr == 6 or sldr == -1): + slider = mc.floatSliderGrp('gsCurveProfileRand', q=1, v=1) + mult = mc.floatSliderGrp('gsCurveProfileRandMulti', q=1, v=1) + value = slider * mult + for i in range(len(sel)): + if mc.attributeQuery('Profile', n=sel[i], ex=1): + if self.curveTrigger == 0: + self.curveProfile.append(mc.getAttr(sel[i] + '.Profile')) + if self.curveRepeat == 1: + mc.setAttr(sel[i] + '.Profile', self.curveProfile[i]) + rand = random.uniform(0, value) + if WIDGETS['gsProfileCheckBoxNegative'].isChecked(): + rand = random.uniform(value * -1, value) + mc.setAttr(sel[i] + '.Profile', rand) + + if slid[7] == 1 and (sldr == 7 or sldr == -1): + if not self.curveRandDragSelList: + self.curveRandDragSelList = sel + if self.timer.increment(1.0 / 60.0): + sliderVal = math.ceil(mc.floatSliderGrp('gsCurveSelectRand', q=1, v=1) * len(self.curveRandDragSelList)) + shuffledList = self.curveRandDragSelList + random.shuffle(shuffledList) + newSel = [] + for i in range(sliderVal): + newSel.append(shuffledList[i]) + if not newSel: + mc.select(self.curveRandDragSelList, r=1) + else: + mc.select(newSel, r=1) + + self.curveRepeat = 1 + self.curveTrigger = 1 + + def randSliderRelease(self, sldr, *_): # Randomize Curve slider release + if self.sliderCheck == 0: + mc.undoInfo(ock=1, cn='curveRandSlider') + self.sliderCheck = 1 + sel = mc.ls(sl=1, fl=1, o=1) + slid = list() + for i in range(8): + slid.append(WIDGETS['curveRandomizeSlider' + str(i)].isChecked()) + + if slid[0] == 1 and sldr == 0: + for i in range(len(self.curveCVsName)): + vect = self.curveCVs[i] + mc.move(vect[0], vect[1], vect[2], self.curveCVsName[i], a=1) + + if slid[1] == 1 and sldr == 1: + for i in range(len(sel)): + rotate = self.curveRotate[i] + mc.setAttr(sel[i] + ".rx", rotate[0]) + mc.setAttr(sel[i] + ".ry", rotate[1]) + mc.setAttr(sel[i] + ".rz", rotate[2]) + + if slid[2] == 1 and sldr == 2: + for i in range(len(sel)): + mc.setAttr(sel[i] + '.Orientation', self.curveOrientation[i]) + + if slid[3] == 1 and sldr == 3: + for i in range(len(self.curveTwist)): + mc.setAttr(sel[i] + '.Twist', self.curveTwist[i]) + + if slid[4] == 1 and sldr == 4: + for i in range(len(sel)): + if mc.attributeQuery('Width', n=sel[i], ex=1): + mc.setAttr(sel[i] + '.Width', self.curveWidth[i]) + else: + mc.setAttr(sel[i] + '.WidthX', self.curveWidthX[i]) + mc.setAttr(sel[i] + '.WidthZ', self.curveWidthZ[i]) + + if slid[5] == 1 and sldr == 5: + for i in range(len(self.curveTaper)): + mc.setAttr(sel[i] + '.Taper', self.curveTaper[i]) + + if slid[6] == 1 and sldr == 6: + for i in range(len(self.curveProfile)): + mc.setAttr(sel[i] + '.Profile', self.curveProfile[i]) + + if slid[7] == 1 and sldr == 7: + if self.curveRandDragSelList: + mc.select(self.curveRandDragSelList, r=1) + + self.curveCVs *= 0 + self.curveCVsName *= 0 + self.curveRotate *= 0 + self.curveOrientation *= 0 + self.curveTwist *= 0 + self.curveWidth *= 0 + self.curveWidthX *= 0 + self.curveWidthZ *= 0 + self.curveTaper *= 0 + self.curveProfile *= 0 + self.curveRandDragSelList *= 0 + self.curveTrigger = 0 + self.curveRepeat = 0 + + def curveControlSliderDrag(self, sldr, *_): # Curve Control slider drag + + if self.sliderCheck == 0: + mc.undoInfo(ock=1, cn='curveControlSlider') + self.sliderCheck = 1 + + sel = mc.ls(sl=1, dag=1, tr=1) + if not sel: + sel = mc.listRelatives(mc.ls(sl=1, o=1), p=1, pa=1) + + if not sel: + return 0 + + sel = list(dict.fromkeys(sel)) + + if not self.timer.increment(1.0 / 30.0) and len(sel) > 10: + return 0 + + attr = sldr.getAttributeName() + value = sldr.getValue() + + for obj in sel: + lengthDivisoinsAttribute = mc.attributeQuery('lengthDivisions', n=obj, ex=1) + lengthAttribute = mc.attributeQuery('Length', n=obj, ex=1) + if not lengthDivisoinsAttribute and not lengthAttribute: + continue + + if attr == 'lineWidth': + mc.setAttr(mc.ls(obj, dag=1, s=1)[0] + '.lineWidth', value) + continue + + if attr == 'WidthX' and WIDGETS['widthLockSwitch'].isChecked(): + try: + mc.setAttr(obj + '.' + 'WidthX', value) + mc.setAttr(obj + '.' + 'WidthZ', value) + WIDGETS['WidthZ'].setValue(value) + continue + except BaseException: + continue + if attr == 'WidthZ' and WIDGETS['widthLockSwitch'].isChecked(): + try: + mc.setAttr(obj + '.' + 'WidthX', value) + mc.setAttr(obj + '.' + 'WidthZ', value) + WIDGETS['WidthX'].setValue(value) + continue + except BaseException: + continue + + if mc.attributeQuery(attr, n=obj, ex=1): + try: + mc.setAttr(obj + '.' + attr, value) + except BaseException: + pass + + def curveHighlightSliderDrag(self): + pass + + +sliders = Sliders("sliders") + + +class ToggleColor: + + COLOR_RANGE = (0.1, 1) + STORAGE_NODE = 'gsColorShaderStorageNode' + + def __init__(self, name): + self.name = name + + # IO + def writeColorDict(self, colorDict): + """Writes a color dict to color storage node""" + self.checkColorStorageNode() + mc.setAttr(self.STORAGE_NODE + '.layerColor', str(colorDict), typ='string') + + def readColorDict(self): + """Reads a color dict from color storage node""" + self.checkColorStorageNode() + dictString = mc.getAttr(self.STORAGE_NODE + '.layerColor') + return eval(dictString) + + def colorEnabled(self): + # type: () -> bool + """Is color enabled?""" + self.checkColorStorageNode() + return bool(mc.getAttr(self.STORAGE_NODE + '.colorApplied')) + + # Check nodes, materials and attributes + + def checkColorStorageNode(self): + """Checks if color storage node exists (and has default values)""" + # Create storage node if not found + if not mc.objExists(self.STORAGE_NODE): + mc.scriptNode(n=self.STORAGE_NODE) + + # Add colorApplied attribute if not found + if not mc.attributeQuery('colorApplied', n=self.STORAGE_NODE, ex=1): + mc.addAttr(self.STORAGE_NODE, ln='colorApplied', at='bool') + mc.setAttr(self.STORAGE_NODE + '.colorApplied', False) + + # Add layerColor attribute if not found + if not mc.attributeQuery('layerColor', n=self.STORAGE_NODE, ex=1): + mc.addAttr(self.STORAGE_NODE, ln='layerColor', dt='string') + mc.setAttr(self.STORAGE_NODE + '.layerColor', + str({k: self.generateBrightColor() for k in range(80)}), typ='string') + else: + colorDict = eval(mc.getAttr(self.STORAGE_NODE + '.layerColor')) + if len(colorDict) < 80: + mc.setAttr(self.STORAGE_NODE + '.layerColor', + str({k: self.generateBrightColor() for k in range(80)}), typ='string') + + # Add layerName attribute if not found + if not mc.attributeQuery('layerName', n=self.STORAGE_NODE, ex=1): + mc.addAttr(self.STORAGE_NODE, ln='layerName', dt='string') + mc.setAttr(self.STORAGE_NODE + '.layerName', str({k: "" for k in range(80)}), typ='string') + else: + if mc.objExists(self.STORAGE_NODE): + storageNode = mc.getAttr(self.STORAGE_NODE + '.layerName') + if storageNode: + layerNames = eval(storageNode) + if len(layerNames) < 80: + mc.setAttr(self.STORAGE_NODE + '.layerName', str({k: "" for k in range(80)}), typ='string') + + # Material creation utils + def getNodeNamesDict(self, layerName): + """Returns a dict with formatted material node names + Valid keys: 'engine', 'shader', 'switch', 'checker', 'tiling' """ + namesDict = {n: 'GSCTMAT_{}_{}'.format(layerName, n) for n in + ['engine', 'shader', 'switchChecker', 'switchDiffuse', 'switchAlpha', 'checker', 'tiling', 'color']} + return namesDict + + def deleteColorMaterial(self, layerName): + """Deletes the named material and all the appropriate nodes""" + nodes = self.getNodeNamesDict(layerName) + for node in nodes: + if mc.objExists(nodes[node]): + mc.delete(nodes[node]) + + def createColorMaterial(self, variantName, originalLayerName=None): + """Creates a single color material network based on the layerName name""" + nodes = self.getNodeNamesDict(variantName) + if not originalLayerName: + originalLayerName = variantName + self.deleteColorMaterial(variantName) # Just in case + # Create all the nodes mc.shadingNode('place2dTexture', au=1, n=place2d, ss=1) + shader = mc.shadingNode('lambert', n=nodes['shader'], ss=1, asShader=1) + engine = mc.sets(r=1, nss=1, em=1, n=nodes['engine']) + switchChecker = mc.shadingNode('condition', n=nodes['switchChecker'], ss=1, au=1) + switchDiffuse = mc.shadingNode('condition', n=nodes['switchDiffuse'], ss=1, au=1) + switchAlpha = mc.shadingNode('condition', n=nodes['switchAlpha'], ss=1, au=1) + checker = mc.shadingNode('checker', n=nodes['checker'], ss=1, at=1) + tiling = mc.shadingNode('place2dTexture', n=nodes['tiling'], ss=1, au=1) + color = mc.shadingNode('colorConstant', n=nodes['color'], ss=1, at=1) + # Set default attributes + mc.setAttr(tiling + '.repeatUV', 10, 10, typ='float2') + mc.setAttr(shader + '.diffuse', 1) + mc.setAttr(color + '.inColor', random.random(), random.random(), random.random(), typ='double3') + mc.setAttr(switchAlpha + '.colorIfFalse', 0, 0, 0, typ='float3') + # Connect stuff together + mc.connectAttr(tiling + '.outUV', checker + '.uvCoord', f=1) + mc.connectAttr(tiling + '.outUvFilterSize', checker + '.uvFilterSize', f=1) + mc.connectAttr(checker + '.outColor', switchChecker + '.colorIfTrue', f=1) + mc.connectAttr(switchChecker + '.outColor', switchDiffuse + '.colorIfTrue') + mc.connectAttr(switchDiffuse + '.outColor', shader + '.color', f=1) + mc.connectAttr(shader + '.outColor', engine + '.surfaceShader', f=1) + mc.connectAttr(color + '.outColor', checker + '.color1', f=1) + mc.connectAttr(color + '.outColor', switchChecker + '.colorIfFalse', f=1) + mc.connectAttr(originalLayerName + '.overrideColorRGB', color + '.inColor') + mc.connectAttr(switchAlpha + '.outColor', shader + '.transparency') + # Create messages + mc.addAttr(engine, ln='gs_shadermessage', at='message') + return engine + + def checkColorMaterial(self, variantName, originalLayerName=None): + """ + Checks if appropriately named material exists for the layer.\n + Also check if material has all the nodes it needs. + """ + if not originalLayerName: + originalLayerName = variantName + nodesDict = self.getNodeNamesDict(variantName) + if not mc.objExists(nodesDict['engine']): + # Create from scratch + self.deleteColorMaterial(variantName) + self.createColorMaterial(variantName, originalLayerName) + else: + # Check if not corrupted + for node in nodesDict: + if not mc.objExists(nodesDict[node]): + self.deleteColorMaterial(variantName) + self.createColorMaterial(variantName, originalLayerName) + break + return nodesDict['engine'] + + # Main methods + def toggleColorVis(self): + """Toggle colors on Color button click or hotkey""" + self.checkColorStorageNode() + if not mc.getAttr(self.STORAGE_NODE + '.colorApplied'): + currentViewport = mc.playblast(ae=1) + if currentViewport: + mc.modelEditor(currentViewport, e=1, displayTextures=1) + self.enableColors() + else: + self.disableColors() + utils.deferredLp(utils.noUndo(updateMainUI))() # TODO: Check if this breaks anything (mostly UNDO) + + def onLayerChange(self, curves, targetLayer): + """Called when curve is moved from layer to layer""" + if WIDGETS['colorMode'].isChecked(): + geo = selectPart(2, True, curves) + shadersDict = utils.getShader(geo) + # Set geometry to original material + for shader in shadersDict: + originalShader = mc.listConnections(shader + '.gs_shadermessage') + if originalShader: + mc.sets(shadersDict[shader], e=1, fe=originalShader[0]) + else: + mc.sets(shadersDict[shader], e=1, fe='initialShadingGroup') + # Disable/Enable target layer + self.disableColors(targetLayer) + self.enableColors(targetLayer) + + def updateColors(self): + """ + Update the colors on cards if color mode is on. Runs every time selection changes.\n + NOTE: Should be as fast as possible + """ + if not WIDGETS['colorMode'].isChecked(): + return + allLayers = [x for x in mc.ls(typ='displayLayer') if ('curveGrp_' in x and '_Geo' in x)] + # Update colors + self.checkColorStorageNode() + colorDict = self.readColorDict() + for layer in allLayers: + split = layer.split('_') + try: + layerID = int(split[1]) if len(split) == 3 else int(split[2]) + except ValueError: + error = 'Failed to extract layer ID from layer "{}". Display Layer name is corrupted. Please delete corrupted layers and curves.'.format(layer) + MESSAGE.warning(error) + continue + r, g, b = colorDict[layerID] + mc.setAttr(layer + '.overrideColorRGB', r, g, b) + + def updateColorOptions(self): + """Triggered when there is need to change checkered or alpha options""" + if not WIDGETS['colorMode'].isChecked(): + return + allMaterialNodes = [x for x in mc.ls() if 'GSCTMAT_' in x] + isChecker = getOption('checkerPattern') + isDiffuseOnly = getOption('colorOnlyDiffuse') + switchNodes = [x for x in allMaterialNodes if '_switchChecker' in x] + for node in switchNodes: + mc.setAttr(node + '.firstTerm', int(not isChecker)) + switchNodes = [x for x in allMaterialNodes if '_switchAlpha' in x] + for node in switchNodes: + mc.setAttr(node + '.firstTerm', int(not isDiffuseOnly)) + + def enableColors(self, oneLayer=None): + """Enable colors on layers""" + allLayers = [x for x in mc.ls(typ='displayLayer') if ('curveGrp_' in x and '_Geo' in x)] + colorDict = self.readColorDict() + isChecker = getOption('checkerPattern') + isDiffuseOnly = getOption('colorOnlyDiffuse') + if oneLayer: + allLayers = [oneLayer] + for layer in allLayers: + # Get layer ID + split = layer.split('_') + try: + layerID = int(split[1]) if len(split) == 3 else int(split[2]) + except ValueError: + error = 'Failed to extract layer ID from layer "{}". Display Layer name is corrupted. Please delete corrupted layers and curves.'.format(layer) + MESSAGE.warning(error) + continue + # Get layer colors and set them + r, g, b = colorDict[layerID] + mc.setAttr(layer + '.overrideColorRGB', r, g, b) + # Get all the shaders for geometry in the layer + layerGeo = mc.editDisplayLayerMembers(layer, q=1, fn=1, nr=1) + shaders = utils.getShader(layerGeo) + count = 0 + for shader in shaders: + # Modify name if there are multiple materials used in one layer + variantName = layer + if len(shaders) > 1: + variantName = variantName + "_variant{}".format(count) + count += 1 + engine = self.checkColorMaterial(variantName, layer) + # Create and connect shadermessage + utils.connectMessage(engine, shader, 'gs_shadermessage') + # Find alpha and color node in the original shader graph and apply it to new color material + namesDict = self.getNodeNamesDict(variantName) + colorNode = None + alphaNode = None + network = mc.hyperShade(lun=shader) + for node in network: + if mc.nodeType(node) == 'file' and mc.connectionInfo(node + '.outColor', isSource=1): + colorNode = node + break + for node in network: + if mc.nodeType(node) == 'file' and mc.connectionInfo(node + '.outTransparency', isSource=1): + alphaNode = node + break + if colorNode: # Just to enable UV editor functionality + mc.connectAttr(colorNode + '.outColor', namesDict['switchDiffuse'] + '.colorIfFalse', f=1) + if alphaNode: # To have transparency with solid color + mc.connectAttr(alphaNode + '.outTransparency', namesDict['switchAlpha'] + '.colorIfTrue', f=1) + # Apply material + mc.sets(shaders[shader], e=1, fe=engine, nw=1) + # Options + if isChecker: + mc.setAttr(namesDict['switchChecker'] + '.firstTerm', 0) + else: + mc.setAttr(namesDict['switchChecker'] + '.firstTerm', 1) + if isDiffuseOnly: + mc.setAttr(namesDict['switchAlpha'] + '.firstTerm', 0) + else: + mc.setAttr(namesDict['switchAlpha'] + '.firstTerm', 1) + mc.setAttr(self.STORAGE_NODE + '.colorApplied', 1) + + def disableColors(self, oneLayer=None): + """Disable colors on layers""" + allColorMaterials = [x for x in mc.ls() if 'GSCTMAT_' in x] + allEngines = [x for x in allColorMaterials if '_engine' in x] + for engine in allEngines: + if oneLayer and oneLayer not in engine: + continue + originalGeo = mc.listConnections(engine + '.dagSetMembers') + if mc.attributeQuery('gs_shadermessage', n=engine, ex=1): + originalShader = mc.listConnections(engine + '.gs_shadermessage') + if originalShader: + mc.sets(originalGeo, e=1, fe=originalShader[0], nw=1) + continue + mc.sets(originalGeo, e=1, fe='initialShadingGroup', nw=1) + for node in allColorMaterials: + if oneLayer and oneLayer not in node: + continue + if mc.objExists(node): + mc.delete(node) + mc.setAttr(self.STORAGE_NODE + '.colorApplied', 0) + + def changeLayerColor(self): + """Called when layer color picker is modified""" + sel = mc.ls(sl=1, tr=1) + clr = mc.colorSliderGrp('gsColorPicker', q=1, rgb=1) + layerList = [] + for obj in sel: + selPart = selectPart(2, True, obj)[0] + conn = mc.listConnections(selPart, s=1, d=0) + layer = mc.ls(conn, et='displayLayer')[0] + layerList.append(layer) + layerList = list(dict.fromkeys(layerList)) + colorDict = self.readColorDict() + for layer in layerList: + layerId = re.findall(r'\d+', layer)[0] + mc.setAttr(layer + '.overrideColorRGB', clr[0], clr[1], clr[2]) + colorDict[int(layerId)] = clr + self.writeColorDict(colorDict) + if WIDGETS['syncCurveColor'].isChecked(): + self.syncCurveColors() + + # Other + def randomizeColors(self, *_): + """Randomizes colors in color storage node and updates UI colors if needed""" + colorDict = self.readColorDict() + for key in colorDict: + geoLayer = 'curveGrp_%s_Geo' % key + r, g, b = self.generateBrightColor() + colorDict[int(key)] = [r, g, b] + if mc.objExists(geoLayer): + mc.setAttr(geoLayer + '.overrideColorRGB', r, g, b) + self.writeColorDict(colorDict) + + if WIDGETS['syncCurveColor'].isChecked(): + self.syncCurveColors() + if mc.objExists(self.STORAGE_NODE) and mc.getAttr(self.STORAGE_NODE + '.colorApplied'): + self.updateColors() + + def syncCurveColors(self, manualSync=False, *_): + """Syncs curve colors to match layer colors""" + self.checkColorStorageNode() + colorDict = self.readColorDict() + sync = WIDGETS['syncCurveColor'].isChecked() + if manualSync: + sync = True + + # Generate color dictionary (if needed) + for i in range(80): + if colorDict[i] == [0, 0, 0]: + colorDict[i] = self.generateBrightColor() + self.writeColorDict(colorDict) + + # Iterate over collections and layers and set colors to curves + layerCollectionsWidget = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + collectionCount = layerCollectionsWidget.count() + for collectionID in range(collectionCount): + collection = "%s_" % collectionID if collectionID > 0 else "" + for i in range(80): + curveLayer = 'curveGrp_%s%s_Curve' % (collection, i) + if not mc.objExists(curveLayer): + continue + curves = mc.editDisplayLayerMembers(curveLayer, q=1, nr=1, fn=1) + for curve in curves: + shape = mc.listRelatives(curve, c=1, typ='nurbsCurve', pa=1)[0] + if sync: + mc.setAttr(shape + '.overrideEnabled', 1) + mc.setAttr(shape + '.overrideRGBColors', 1) + mc.setAttr(shape + '.overrideColorRGB', colorDict[i][0], colorDict[i][1], colorDict[i][2]) + curveControlUI.updateUI() + + def resetCurveColors(self, *_): + """Resets curve colors to default value""" + self.checkColorStorageNode() + layerCollectionsWidget = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + collectionCount = layerCollectionsWidget.count() + for collectionID in range(collectionCount): + collection = "%s_" % collectionID if collectionID > 0 else "" + for i in range(80): + curveLayer = 'curveGrp_%s%s_Curve' % (collection, i) + if not mc.objExists(curveLayer): + continue + curves = mc.editDisplayLayerMembers(curveLayer, q=1, nr=1, fn=1) + for curve in curves: + shape = mc.listRelatives(curve, c=1, typ='nurbsCurve', pa=1)[0] + mc.setAttr(shape + '.overrideEnabled', 0) + curveControlUI.updateUI() + + def changeCurveColor(self, *_): + """Called when curve color picker is modified""" + sync = WIDGETS['syncCurveColor'].isChecked() + if sync: + updateMainUI() + curveControlUI.updateUI() + else: + sel = mc.ls(sl=1, dag=1, s=1, typ='nurbsCurve') + clr = mc.colorSliderGrp('gsCurveColorPicker', q=1, rgb=1) + for crv in sel: + if mc.nodeType(crv) != 'nurbsCurve': + continue + if clr != [0, 0, 0]: + mc.setAttr(crv + '.overrideEnabled', 1) + mc.setAttr(crv + '.overrideRGBColors', 1) + mc.setAttr(crv + '.overrideColorRGB', clr[0], clr[1], clr[2]) + else: + mc.setAttr(crv + '.overrideEnabled', 0) + + # Utility methods + def generateBrightColor(self, satMin=0.5, satMax=1.0): + """Create random bright color and return as RGB tuple""" + h, s, l = random.random(), random.uniform(satMin, satMax), random.uniform(0.3, 0.7) + r, g, b = colorsys.hls_to_rgb(h, l, s) + return (r, g, b) + + def resetSingleCurve(self, curve): + shape = mc.listRelatives(curve, c=1, typ='nurbsCurve', pa=1)[0] + mc.setAttr(shape + '.overrideEnabled', 0) + + +toggleColor = ToggleColor("toggleColor") + + +class CurveControlUI: + + extrudeCard = { + 'lengthDivisions', + 'dynamicDivisions', + 'widthDivisions', + 'Orientation', + 'Twist', + 'Width', + 'Taper', + 'Profile', + 'curveRefine', + 'autoRefine', + 'curveSmooth', + 'surfaceNormals', + 'reverseNormals', + } + + extrudeTube = { + 'lengthDivisions', + 'dynamicDivisions', + 'widthDivisions', + 'Orientation', + 'Twist', + 'widthComboSlider', + 'Taper', + 'curveRefine', + 'autoRefine', + 'curveSmooth', + 'surfaceNormals', + 'reverseNormals', + } + + warpCard = { + 'lengthDivisions', + 'dynamicDivisions', + 'widthDivisions', + 'Orientation', + 'Twist', + 'invTwist', + 'twistCurveFrame', + 'Width', + 'Taper', + 'widthCurveFrame', + 'LengthLock', + 'Length', + 'Offset', + 'Profile', + 'profileCurveGraph', + 'curveRefine', + 'autoRefine', + 'curveSmooth', + 'samplingAccuracy', + 'surfaceNormals', + 'reverseNormals', + 'Magnitude' + } + + warpTube = { + 'lengthDivisions', + 'dynamicDivisions', + 'widthDivisions', + 'Orientation', + 'Twist', + 'twistCurveFrame', + 'widthComboSlider', + 'Taper', + 'widthCurveFrame', + 'LengthLock', + 'Length', + 'Offset', + 'curveRefine', + 'autoRefine', + 'curveSmooth', + 'samplingAccuracy', + 'surfaceNormals', + 'reverseNormals', + 'Magnitude' + } + + bind = { + 'axisFrame', + 'editOrigObj', + 'Orientation', + 'twistCurveFrame', + 'Width', + 'widthCurveFrame', + 'LengthLock', + 'Length', + 'Offset', + 'curveRefine', + 'autoRefine', + 'curveSmooth', + 'samplingAccuracy', + 'reverseNormals', + 'Magnitude' + } + + # Set of all controls + allControls = extrudeCard \ + | extrudeTube \ + | warpCard \ + | warpTube \ + | bind + + def __init__(self, name): + self.name = name + + def updateUI(self): + """Updates all sliders and buttons in Curve Control Window and Connect Graphs""" + if not mc.workspaceControl(CURVE_CONTROL_NAME, q=1, ex=1): + return + + # Update advanced visibility window + advancedVisibility.updateUI() + + # Apply geometry highlight if enabled + if mc.optionVar(q='GSCT_GeometryHighlightEnabled'): + utils.noUndo(advancedVisibility.geoHighlight)() + + # Meshes Section + meshSel = mc.filterExpand(mc.ls(o=1, sl=1), sm=12) + + if meshSel: + WIDGETS['orientToNormalsFrame'].setVisible(True) + else: + WIDGETS['orientToNormalsFrame'].setVisible(False) + + # Curves Section + sel = mc.filterExpand(mc.ls(o=1, sl=1), sm=9) + if not sel: + sel = mc.ls(o=1, hl=1) + + if len(sel) == 0: + self.hideControls() + return + + curve = sel[-1] # Use only last curve to update the UI + + if mc.nodeType(curve) == 'nurbsCurve': + rel = mc.listRelatives(curve, p=1, pa=1) + curve = rel[0] + + # Get all attributes from the curve + self.sliderAttr = attributes.getAttr(curve) + + if 'Orientation' not in self.sliderAttr: + self.hideControls() + return + + if not mc.connectionInfo(curve + '.Orientation', isSource=1): + self.hideControls() + return + + # Enable orient to normals frame (if curves are selected but not the geo) + WIDGETS['orientToNormalsFrame'].setVisible(True) + + # Disable prompt + WIDGETS['selectCurvesPrompt'].setVisible(False) + + # Enable header and footer + WIDGETS['gsCurveControlHeader'].setEnabled(True) + WIDGETS['resetControlSliders'].setVisible(True) + + # Update Geometry Color and Layer Number + try: + layer = mc.ls(mc.listConnections(selectPart(2, True, curve)[0], s=1, d=0), et='displayLayer')[0] + + rgb = utils.getAttr(layer, 'overrideColorRGB') + + ind = int(re.findall(r'\d+', layer)[0]) + + # If RGB is zero, check the storage node + if rgb == [(0, 0, 0)]: + colorDict = toggleColor.readColorDict() + if ind in colorDict: + rgb = [colorDict[ind]] + WIDGETS['gsColorPicker'].setRGBColors(*rgb) + WIDGETS['gsLayerSelector'].setCurrentIndex(ind) + except Exception as e: + WIDGETS['gsLayerSelector'].setCurrentIndex(0) + WIDGETS['gsColorPicker'].setRGBColors([0, 0, 0]) + LOGGER.exception(e) + + # Update Curve Color + try: + shape = mc.ls(curve, dag=1, s=1)[0] + if mc.getAttr(shape + '.overrideEnabled'): + curveRGB = utils.getAttr(shape, 'overrideColorRGB') + WIDGETS['gsCurveColorPicker'].setRGBColors(*curveRGB) + else: + WIDGETS['gsCurveColorPicker'].setRGBColors([0, 0, 0]) + except Exception as e: + WIDGETS['gsCurveColorPicker'].setRGBColors([0, 0, 0]) + LOGGER.exception(e) + + # Update Text Field + try: + WIDGETS['selectedObjectName'].setText(selectPart(0, True, curve)[0]) + except BaseException: + WIDGETS['selectedObjectName'].setText('') + + # Set the dynamic divisions and auto refine false by default + WIDGETS['dynamicDivisions'].setChecked(False) + WIDGETS['autoRefine'].setChecked(False) + WIDGETS['curveRefine'].setEnabled(True) + WIDGETS['curveSmooth'].setEnabled(True) + + # Update main sliders and checkboxes + for attr in self.sliderAttr: + if attr == 'dynamicDivisions' and attr in WIDGETS: + WIDGETS['dynamicDivisions'].setChecked(bool(mc.getAttr(curve + '.dynamicDivisions'))) + continue + + # Set "Other" frame visibility + if attr == 'curveRefine' and attr in WIDGETS: + WIDGETS['otherFrame'].setVisible(True) + + # Update Auto-Refine, Curve Refine and Curve Smooth sliders and toggles + if attr == 'autoRefine' and attr in WIDGETS: + WIDGETS['autoRefine'].setChecked(bool(mc.getAttr(curve + '.autoRefine'))) + WIDGETS['curveRefine'].setEnabled(not bool(mc.getAttr(curve + '.autoRefine'))) + WIDGETS['curveSmooth'].setEnabled(not bool(mc.getAttr(curve + '.autoRefine'))) + continue + + # Update Axis switch and Edit Original Obj. checkbox + if attr == 'Axis' and attr in WIDGETS: + WIDGETS['Axis'].button(self.sliderAttr['Axis']).setChecked(True) + rebuildCurve = mc.listConnections(curve + '.curveSmooth', scn=1)[0] + warp = mc.listConnections(rebuildCurve + '.outputCurve', scn=1) + WIDGETS['editOrigObj'].setChecked(not mc.getAttr(warp[0] + '.envelope')) + continue + + # Update Reverse Normals Attribute + if attr == 'reverseNormals' and attr in WIDGETS: + WIDGETS['reverseNormals'].setValue(not self.sliderAttr[attr]) + continue + + # Update Flip UV Attribute + if attr == 'flipUV' and attr in WIDGETS: + WIDGETS['flipUV'].setValue(not self.sliderAttr[attr]) + continue + + # Update the rest of the attributes + if attr in WIDGETS: + WIDGETS[attr].setValue(self.sliderAttr[attr]) + + # Check for legacy UV attributes + if 'rotateTipUV' in self.sliderAttr: + WIDGETS['rotateTipUV'].setVisible(True) + WIDGETS['rotateRootUV'].setVisible(True) + else: + WIDGETS['rotateTipUV'].setVisible(False) + WIDGETS['rotateRootUV'].setVisible(False) + + # Update profile graph + if mc.attributeQuery('latticeMessage', n=curve, ex=1): + lattice = mc.listConnections(curve + '.latticeMessage') + if lattice: + latticeDiv = mc.getAttr(lattice[0] + '.sDivisions') + if latticeDiv: + currentPoints = [] + if lattice: + for i in range(latticeDiv): + currentPoints.append(mc.pointPosition(lattice[0] + '.pt[%s][1][0]' % (i), l=1)) + + newPoints = [] + for i in range(latticeDiv): + x = mt.lerp(currentPoints[i][0], 1, 0, 0.5, -0.5) + y = mt.lerp(currentPoints[i][1], 1, 0, 1.5, -0.5) + newPoints.append((x, y)) + + graphPoints = '' + + for i in range(latticeDiv): + graphPoints += '%s,%s,' % (newPoints[i][0], newPoints[i][1]) + + WIDGETS['profileCurve'].setGraph(graphPoints) + if mc.workspaceControl('GSCT_ProfileGraphPopOut', ex=1) and 'profileCurve_large' in WIDGETS: + WIDGETS['profileCurve_large'].setGraph(graphPoints) + + # Connect Warp Graphs + if 'Length' in self.sliderAttr: + rebuildCurve = mc.listConnections(curve + '.curveSmooth', scn=1) + if rebuildCurve: + warp = mc.listConnections(rebuildCurve[0] + '.outputCurve', scn=1) + if warp: + WIDGETS['twistCurve'].connectGraph(warp[0] + '.twistCurve') + WIDGETS['scaleCurve'].connectGraph(warp[0] + '.scaleCurve') + + # Connect Large Graphs if exist + if mc.workspaceControl("GSCT_TwistGraphPopOut", ex=1) and 'twistCurve_large' in WIDGETS: + if warp: + WIDGETS['twistCurve_large'].connectGraph(warp[0] + '.twistCurve') + WIDGETS['Magnitude_large'].setValue(self.sliderAttr['Magnitude']) + if mc.workspaceControl("GSCT_WidthGraphPopOut", ex=1) and 'scaleCurve_large' in WIDGETS: + if warp: + WIDGETS['scaleCurve_large'].connectGraph(warp[0] + '.scaleCurve') + + # Show/Hide current controls + currentControls = False + uvs = False + solidify = False + + if 'lengthDivisions' in self.sliderAttr and 'LengthLock' not in self.sliderAttr: + if 'Width' in self.sliderAttr: + currentControls = self.extrudeCard + else: + currentControls = self.extrudeTube + elif 'lengthDivisions' in self.sliderAttr and 'LengthLock' in self.sliderAttr: + if 'Width' in self.sliderAttr: + currentControls = self.warpCard + else: + currentControls = self.warpTube + elif 'AxisFlip' in self.sliderAttr: + currentControls = self.bind + else: + self.hideControls() + return 0 + + if 'solidify' in self.sliderAttr: + solidify = True + + if 'moveU' in self.sliderAttr: + uvs = True + + self.changeControls(currentControls, uvs, solidify) + + def changeControls(self, currentControls, uv=True, solidify=True): + if not currentControls: + self.hideControls() + return 0 + + hiddenControls = self.allControls - currentControls + for key in currentControls: + if key in WIDGETS: + WIDGETS[key].setVisible(True) + for key in hiddenControls: + if key in WIDGETS: + WIDGETS[key].setVisible(False) + + WIDGETS['UVFrame'].setVisible(True if uv else False) + WIDGETS['solidifyFrame'].setVisible(True if solidify else False) + + def hideControls(self): + for key in self.allControls: + if key in WIDGETS: + WIDGETS[key].setHidden(True) + WIDGETS['gsCurveControlHeader'].setEnabled(False) + WIDGETS['gsColorPicker'].setRGBColors([0, 0, 0]) + WIDGETS['gsCurveColorPicker'].setRGBColors([0, 0, 0]) + WIDGETS['gsLayerSelector'].setCurrentIndex(0) + WIDGETS['selectedObjectName'].setText('') + WIDGETS['otherFrame'].setVisible(False) + WIDGETS['UVFrame'].setVisible(False) + WIDGETS['solidifyFrame'].setVisible(False) + WIDGETS['resetControlSliders'].setVisible(False) + WIDGETS['selectCurvesPrompt'].setVisible(True) + + def selectOriginalObjects(self): + """Selects the curves that were attached to the bind curve""" + sel = mc.ls(sl=1, tr=1) + if not sel: + return + selectionList = [] + for curve in sel: + if not mc.attributeQuery('gsmessage', n=curve, ex=1): + continue + childCards = mc.listConnections(curve + '.gsmessage', d=1, s=0, t='transform', scn=1) + if not childCards: + continue + selectionList += childCards + if selectionList: + mc.select(selectionList, r=1) + + def editOriginalObjects(self): + """Temporarily unhides the original objects from the bind curve""" + sel = mc.ls(sl=1, tr=1) + if not sel: + return + checkBox = WIDGETS['editOrigObj'].isChecked() + axisFlip = WIDGETS['AxisFlip'].isChecked() + for curve in sel: + try: + getAttr = attributes.getAttr(curve) + if 'AxisFlip' in getAttr: + warpNode = mc.listConnections(curve + '.Width')[0] + message = mc.listConnections(curve + '.gsmessage') + if message: + grp = mc.listRelatives(mc.listRelatives(message[0], p=1, pa=1), p=1, pa=1) + if checkBox: + if grp: + mc.setAttr(warpNode + '.envelope', 0) + mc.setAttr(grp[0] + '.visibility', 1) + else: + if grp: + mc.setAttr(warpNode + '.envelope', 1) + mc.setAttr(grp[0] + '.visibility', 0) + if axisFlip and mc.attributeQuery('reverseNormals', n=curve, ex=1): + mc.setAttr(curve + '.reverseNormals', not mc.getAttr(curve + '.reverseNormals')) + elif not message and 'AxisFlip' in getAttr: + if checkBox: + mc.setAttr(warpNode + '.envelope', 0) + else: + mc.setAttr(warpNode + '.envelope', 1) + if axisFlip and mc.attributeQuery('reverseNormals', n=curve, ex=1): + mc.setAttr(curve + '.reverseNormals', not mc.getAttr(curve + '.reverseNormals')) + self.updateUI() + except Exception as e: + LOGGER.exception(e) + + +curveControlUI = CurveControlUI("curveControlUI") + + +class ImportExport: + """ Import and Export curves """ + + def __init__(self, name): + self.name = name + + def exportCurves(self): + """Export selected curves into a .curves or .ma file to import later""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, o=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one curve to export') + return + mc.select(sel) + sel = selectPart(0, True) + if not sel: + return + mc.select(sel, r=1) + mc.select(hi=1) + filters = "GS Curves (.curves) (*.curves);;Maya ASCII (.ma) (*.ma)" + dialog = mc.fileDialog2(fileFilter=filters, dialogStyle=2) + if not dialog: + return + mc.file(dialog, force=1, options="v=0;", typ="mayaAscii", pr=1, es=1, de=0) + + def importCurves(self): + """Import curves from .curve or .ma file that was exported using exportCurves function""" + filters = "GS Curves (.curves) (*.curves);;Maya ASCII (.ma) (*.ma)" + dialog = mc.fileDialog2(fileFilter=filters, fm=1, dialogStyle=2, okc='Open') + if not dialog: + return + nodes = mc.file(dialog, i=1, dns=1, rnn=1) + curveGroups = [] + geoGroups = [] + instGroups = [] + collection = None + if getOption('importIntoANewCollection') and getOption('showLayerCollectionsMenu'): + collection = layerCollections.createImportedCurvesCollection() + for node in nodes: + if 'curveGrp_' in node and '_Curve' in node: + curveGroups.append(node) + if 'curveGrp_' in node and '_Geo' in node: + geoGroups.append(node) + if 'curveGrp_' in node and '_Inst' in node: + instGroups.append(node) + for group in curveGroups: + split = group.split("_") + grpCurves = mc.editDisplayLayerMembers(group, q=1, fn=1) + try: + index = int(split[1]) if len(split) == 3 else int(split[2]) + except ValueError: + error = 'Failed to extract layer ID from layer "{}". Display Layer name is corrupted. Please delete corrupted layers and curves.'.format(group) + MESSAGE.warning(error) + continue + curveAddToLayer(index, inputCurves=grpCurves, targetCollection=collection) + utils.fixDuplicateNames(nodes) + # Check if there are some empty groups left after the import + grps = curveGroups + geoGroups + instGroups + for grp in grps: + if mc.objExists(grp) and not mc.editDisplayLayerMembers(grp, q=1, fn=1): + mc.delete(grp) + + +importExport = ImportExport("importExport") + + +class LayerCollections: + + def __init__(self, name): + self.name = name + self.copyIndex = None + + ### INTERFACE METHODS ### + + def toggleLayerCollectionsWidget(self): + """Toggles the visibility of the layer collections widget""" + if getOption('showLayerCollectionsMenu'): + WIDGETS['LayerCollectionsLayout'].setHidden(False) + else: + WIDGETS['LayerCollectionsLayout'].setHidden(True) + WIDGETS['layerCollectionsComboBox'].setCurrentIndex(0) + WIDGETS['LayerLayout'].update() + WIDGETS['LayerLayout'].parentWidget().update() + + ### PARAMETERS UPDATES ### + + def updateDefaultLayerNode(self): + """Updates layer collection dict attribute on defaultLayer node based on collections combo box items""" + if not mc.objExists('defaultLayer'): + MESSAGE.warning("Default layer not found!") + return + if not mc.attributeQuery('gsCollectionsDict', n='defaultLayer', ex=1): + mc.addAttr('defaultLayer', ln='gsCollectionsDict', dt='string') + comboBox = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + collectionsDict = {} + for i in range(1, comboBox.count()): + collectionsDict.update({i: comboBox.itemText(i)}) + mc.setAttr('defaultLayer.gsCollectionsDict', str(collectionsDict), typ='string') + + def updateCollectionNames(self): + """Updates all the active collection layers to have a proper name based gsCollectionName attr on curves""" + collectionSet = utils.getCollectionsSet() + if not collectionSet: + return + allLayers = [x for x in mc.ls(typ='displayLayer') if ('curveGrp_' in x and '_Curve' in x)] + filteredLayers = [x for x in allLayers if len(x.split("_")) == 4] + for c in filteredLayers: + if not mc.objExists(c): + continue + if not mc.attributeQuery('gsCollectionName', n=c, ex=1): + mc.addAttr(c, ln='gsCollectionName', dt='string') + collectionID = int(c.split("_")[1]) + collectionName = WIDGETS['layerCollectionsComboBox'].itemText(collectionID) + mc.setAttr(c + '.gsCollectionName', collectionName, typ='string') + + ### FUNCTIONAL METHODS ### + + def createImportedCurvesCollection(self): + # type: () -> int + """Creates and Imported Curves collection and returns its ID""" + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + collectionId = layerDropdownMenu.findText("ImportedCurves") + if collectionId == -1: + layerDropdownMenu.addItem("ImportedCurves") + collectionId = layerDropdownMenu.count() - 1 + layerDropdownMenu.setCurrentIndex(collectionId) + return collectionId + + def createLayerCollection(self, exists=False): + # type: (bool) -> None + """Create a named layer collection via the plus button on the interface""" + msg = 'Name already exists.\nEnter unique name:' if exists else 'Enter Collection Name:' + result = mc.promptDialog( + title='New Collection', + message=msg, + button=['OK', 'Cancel'], + defaultButton='OK', + cancelButton='Cancel', + dismissString='Cancel' + ) + if result == 'OK': + name = mc.promptDialog(q=1, t=1) + else: + return + if not name: + MESSAGE.warningInView("Invalid Name!") + self.createLayerCollection() + return + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + if layerDropdownMenu.findText(name) != -1: + self.createLayerCollection(True) + return + layerDropdownMenu.addItem(name) + layerDropdownMenu.setCurrentIndex(layerDropdownMenu.count() - 1) + self.updateDefaultLayerNode() + updateMainUI() + + def deleteLayerCollection(self): + """Delete currently active layer collection and transfer all the layers to the current-1 collection""" + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + index = layerDropdownMenu.currentIndex() + if index == 0: + return + name = layerDropdownMenu.itemText(index) + result = mc.confirmDialog( + title='Delete Collection', + message='Delete collection "{}" (#{})?\nAll the layers will be transferred one collection up.'.format(name, index), + icn='information', + button=['OK', 'Cancel'], + defaultButton='OK', + cancelButton='Cancel', + dismissString='Cancel' + ) + if result != 'OK': + return + self.transferLayerCollection(index, index - 1) + layerDropdownMenu.setCurrentIndex(index - 1) + layerDropdownMenu.removeItem(index) + self.updateDefaultLayerNode() + count = layerDropdownMenu.count() + for i in range(1, count): + self.transferLayerCollection(index + i, index - 1 + i) + self.updateDefaultLayerNode() + updateMainUI() + + def transferLayerCollection(self, sourceIndex, targetIndex): + # type: (int, int, bool) -> None + """Transfers all the layers from source collection to the target collection""" + allLayers = [x for x in mc.ls(typ='displayLayer') if ('curveGrp_' in x and '_Curve' in x)] + sourceLayers = [] + for layer in allLayers: + s = layer.split('_') + if len(s) == 4 and int(s[1]) == sourceIndex: + sourceLayers.append(layer) + for layer in sourceLayers: + s = layer.split('_') + targetLayer = s[2] if len(s) == 4 else s[1] + curves = mc.editDisplayLayerMembers(layer, q=1, fn=1) + if not curves: + continue + curveAddToLayer(targetLayer=targetLayer, inputCurves=curves, targetCollection=targetIndex) + + def mergeUp(self): + """Transfer all the layers from the current collection up and shifts other collections""" + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = layerDropdownMenu.currentIndex() + count = layerDropdownMenu.count() + if currentIndex > 0: + upIndex = currentIndex - 1 + self.transferLayerCollection(currentIndex, upIndex) + layerDropdownMenu.setCurrentIndex(upIndex) + layerDropdownMenu.removeItem(currentIndex) + self.updateDefaultLayerNode() + for i in range(1, count): + self.transferLayerCollection(currentIndex + i, upIndex + i) + self.updateDefaultLayerNode() + updateMainUI() + else: + MESSAGE.warningInView('This is the first collection in the list.') + + def mergeDown(self): + """Transfer all the layers from the current collection down and shift all the collections""" + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = layerDropdownMenu.currentIndex() + count = layerDropdownMenu.count() + if currentIndex == 0: + MESSAGE.warningInView("Main collection can't be merged.") + return + if currentIndex == count - 1: + MESSAGE.warningInView('This is the last collection in the list.') + return + downIndex = currentIndex + 1 + self.transferLayerCollection(currentIndex, downIndex) + layerDropdownMenu.setCurrentIndex(downIndex) + layerDropdownMenu.removeItem(currentIndex) + self.updateDefaultLayerNode() + for i in range(1, count): + self.transferLayerCollection(currentIndex + i, currentIndex - 1 + i) + self.updateDefaultLayerNode() + updateMainUI() + + def moveDown(self): + """Moves current collection index down, rearranging the collection""" + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = layerDropdownMenu.currentIndex() + count = layerDropdownMenu.count() + if currentIndex == 0: + MESSAGE.warningInView("Main collection can't be moved.") + return + if currentIndex == count - 1: + MESSAGE.warningInView('This is the last collection in the list.') + return + currentName = layerDropdownMenu.itemText(currentIndex) + downIndex = currentIndex + 1 + downName = layerDropdownMenu.itemText(downIndex) + layerDropdownMenu.addItem('GS_TEMP_COLLECTION_DELETE_THIS') + layerDropdownMenu.setItemText(downIndex, currentName) + layerDropdownMenu.setItemText(currentIndex, downName) + self.transferLayerCollection(currentIndex, layerDropdownMenu.count() - 1) + self.transferLayerCollection(downIndex, currentIndex) + self.transferLayerCollection(layerDropdownMenu.count() - 1, downIndex) + layerDropdownMenu.removeItem(layerDropdownMenu.count() - 1) + self.updateDefaultLayerNode() + updateMainUI() + + def moveUp(self): + """Moves current collection index down, rearranging the collection""" + layerDropdownMenu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = layerDropdownMenu.currentIndex() + if currentIndex == 0 or currentIndex == 1: + MESSAGE.warningInView("Main collection can't be moved.") + return + currentName = layerDropdownMenu.itemText(currentIndex) + upIndex = currentIndex - 1 + upName = layerDropdownMenu.itemText(upIndex) + layerDropdownMenu.addItem('GS_TEMP_COLLECTION_DELETE_THIS') + layerDropdownMenu.setItemText(upIndex, currentName) + layerDropdownMenu.setItemText(currentIndex, upName) + self.transferLayerCollection(currentIndex, layerDropdownMenu.count() - 1) + self.transferLayerCollection(upIndex, currentIndex) + self.transferLayerCollection(layerDropdownMenu.count() - 1, upIndex) + layerDropdownMenu.removeItem(layerDropdownMenu.count() - 1) + self.updateDefaultLayerNode() + updateMainUI() + + def rename(self, exists=False): + menu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = menu.currentIndex() + if currentIndex == 0: + MESSAGE.warningInView("Main collection can't be renamed.") + return + msg = "Name already exists.\nEnter unique name:" if exists else "Enter New Name:" + result = mc.promptDialog( + title='Rename Collection', + message=msg, + button=['OK', 'Cancel'], + defaultButton='OK', + cancelButton='Cancel', + dismissString='Cancel' + ) + if result == 'OK': + name = mc.promptDialog(q=1, t=1) + else: + return + if not name: + MESSAGE.warningInView("Invalid Name!") + self.rename() + return + if menu.findText(name) != -1: + self.rename(True) + return + menu.setItemText(currentIndex, name) + self.updateDefaultLayerNode() + updateMainUI() + + def clear(self): + menu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = menu.currentIndex() + itemName = menu.itemText(currentIndex) + msg = 'Clearing collection "{}".\nAll the layers and curves in this collection will be deleted.\nAre you sure?'.format(itemName) + result = mc.confirmDialog( + title='Clear Collection', + message=msg, + icn='warning', + button=['OK', 'Cancel'], + defaultButton='OK', + cancelButton='Cancel', + dismissString='Cancel' + ) + if result == 'OK': + for i in range(80): + curveGrp, _, _ = utils.getFormattedLayerNames(currentIndex, i) + if not mc.objExists(curveGrp): + continue + contents = mc.editDisplayLayerMembers(curveGrp, q=1, fn=1) + if not contents: + continue + for curve in contents: + if not mc.objExists(curve): + continue + if mc.attributeQuery('gsmessage', ex=1, n=curve): + if mc.connectionInfo(curve + '.gsmessage', isDestination=1): + continue + parentGrp = mc.listRelatives(curve, p=1) + if parentGrp: + mc.delete(parentGrp) + self.updateDefaultLayerNode() + updateMainUI() + + def copy(self): + menu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = menu.currentIndex() + self.copyIndex = currentIndex + MESSAGE.warningInView('Layer collection "%s" added to buffer' % menu.itemText(currentIndex)) + + def paste(self): + if self.copyIndex is None: + return + menu = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentIndex = menu.currentIndex() + if self.copyIndex == currentIndex: + return + MESSAGE.warningInView('Pasting from collection "%s" to "%s"' + % (menu.itemText(self.copyIndex), menu.itemText(currentIndex))) + + allLayers = [x for x in mc.ls(typ='displayLayer') if ('curveGrp_' in x and '_Curve' in x)] + sourceLayers = [] + if self.copyIndex == 0: + sourceLayers = [x for x in allLayers if len(x.split("_")) == 3] + else: + for layer in allLayers: + s = layer.split("_") + if len(s) == 4 and int(s[1]) == self.copyIndex: + sourceLayers.append(layer) + for layer in sourceLayers: + s = layer.split("_") + targetLayer = s[2] if len(s) == 4 else s[1] + curves = mc.editDisplayLayerMembers(layer, q=1, fn=1) + if not curves: + continue + curves = list(duplicateCurve(customSel=curves)) + curveAddToLayer(targetLayer=targetLayer, inputCurves=curves, targetCollection=currentIndex) + self.updateDefaultLayerNode() + self.copyIndex = None + + +layerCollections = LayerCollections("layerCollections") + + +class AdvancedVisibility(): + + CACHED_GEO = [] + + def __init__(self, name): + self.name = name + + def geoHighlight(self): + """ Update geometry hilite based on the selected curve """ + # Check if window exists + if not mc.workspaceControl(CURVE_CONTROL_NAME, q=1, ex=1): + return + if not mc.optionVar(q="GSCT_GeometryHighlightEnabled"): + mc.hilite(self.CACHED_GEO, u=1) + self.CACHED_GEO = [] + WIDGETS['geometryHighlight'].setChecked(False) + return + else: + WIDGETS['geometryHighlight'].setChecked(True) + sel = mc.ls(sl=1, dag=1, typ='nurbsCurve') + if not sel: + sel = mc.ls(sl=1, o=1, typ='nurbsCurve') + if not sel: + sel = mc.ls(hl=1, dag=1, typ='nurbsCurve') + if sel: + geo = selectPart(2, True, sel) + if geo: + mc.hilite(geo) + self.CACHED_GEO = geo + else: + self.CACHED_GEO = [] + else: + try: + mc.hilite(self.CACHED_GEO, u=1) + except BaseException: + mc.hilite([], r=1) + self.CACHED_GEO = [] + + def geometryHighlightCommand(self): + mc.optionVar(iv=["GSCT_GeometryHighlightEnabled", not mc.optionVar(q='GSCT_GeometryHighlightEnabled')]) + self.geoHighlight() + + def createNode(self): + tr = mc.createNode('transform', n='GSCT_CurveTools_DrawManager', ss=1) + # Lock transforms for node + for attr in ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'shearXY', 'shearXZ', 'shearYZ']: + mc.setAttr(tr + '.' + attr, l=1, k=0) + # Create rest of the network + mc.createNode('GSCT_CurveTools_DrawManagerNode', + n='GSCT_CurveTools_DrawManagerNode', p=tr, ss=1) + mc.setAttr(tr + ".useOutlinerColor", 1) + mc.setAttr(tr + ".outlinerColor", 0, 1, 0, typ="double3") + mc.reorder(tr, r=1) + self.applySettingsToNode() + + def updateUI(self): + """Updates UI on selection change""" + if not all(mc.getClassification("GSCT_CurveTools_DrawManagerNode")): # Check if node type exists + return + button = WIDGETS['curveHighlight'] + frame = WIDGETS['gsCurveHighlightFrame'] + nodes = mc.ls(typ='GSCT_CurveTools_DrawManagerNode') + if not nodes: + button.setChecked(False) + frame.setEnabled(False) + nodeFound = False + for node in nodes: + if mc.referenceQuery(node, inr=1): + mc.setAttr(node + '.nodeState', 1) + continue + nodeFound = True + if nodeFound: + WIDGETS['curveHighlight'].setChecked(True) + WIDGETS['gsCurveHighlightFrame'].setEnabled(True) + else: + WIDGETS['curveHighlight'].setChecked(False) + WIDGETS['gsCurveHighlightFrame'].setEnabled(False) + + def removeUnknownNodes(self): + """Finds all the nodes that were possibly left out from the previous session""" + sel = mc.ls(typ="unknownDag") + for n in sel: + if "GSCT_CurveTools_DrawManager" in n: + mc.delete(mc.listRelatives(n, p=1, pa=1)) + + def toggleCurveHighlightFromUI(self, hotkey=False): + """Toggles the nodes in the scene from UI""" + # Check if there are leftover nodes + self.removeUnknownNodes() + # Check if control exists + if 'curveHighlight' not in WIDGETS: + from . import ui + ui.curveControlWorkspace() + return + # Check for Maya version and find an appropriate plug-in to load + button = WIDGETS['curveHighlight'] # type: wrap.Button + pluginPath = utils.getFolder.plugins() + winPath = os.path.join(pluginPath, str(MAYA_VER), "cv_manip.mll") + fallBackPath = os.path.join(pluginPath, "cv_manip.py") + result = False + if OS != "mac": + result = utils.loadCustomPlugin(winPath) + else: + result = utils.loadCustomPlugin(fallBackPath) + # If loading fails, try to load the fallback (python) version instead + if not result: + finalResult = utils.loadCustomPlugin(fallBackPath) + if not finalResult: + MESSAGE.warning( + "Can't load cv_manip plug-in. Check your installation or contact the developer: george.sladkovsky@gmail.com") + button.setChecked(False) + return + # Check if node type exists + if not all(mc.getClassification("GSCT_CurveTools_DrawManagerNode")): + button.setChecked(False) + MESSAGE.warning( + "No compatible node types found. Please reinstall GS CurveTools or send a bug report: george.sladkovsky@gmail.com") + return + # Proceed with node creation/activation + nodes = mc.ls(typ='GSCT_CurveTools_DrawManagerNode') + if button.isChecked() if hotkey else not button.isChecked(): + # Delete nodes from scene if found + for node in nodes: + if mc.referenceQuery(node, inr=1): + mc.setAttr(node + '.nodeState', 1) + else: + mc.delete(mc.listRelatives(node, p=1, pa=1)) + else: + # Just create a new node + self.createNode() + # Check if locator visibility is enabled in the current view + try: + currentView = mc.playblast(ae=1) + mc.modelEditor(currentView, e=1, locators=1) + except BaseException: + pass + self.updateUI() + + # ---- Saving, loading and applying settings ---- + + def loadSettingsFromOptionVar(self): + """Load settings from optionVars and apply them to the interface and to the node""" + if not mc.workspaceControl(CURVE_CONTROL_NAME, q=1, ex=1): + return + # Load ints (bools) + WIDGETS['curveVisibility'].setChecked(mc.optionVar(q='GSCT_' + 'gsCurveVisibilityToggle')) + WIDGETS['hullVisibility'].setChecked(mc.optionVar(q='GSCT_' + 'gsHullVisibilityToggle')) + WIDGETS['lazyUpdate'].setChecked(mc.optionVar(q='GSCT_' + 'gsLazyUpdateToggle')) + WIDGETS['alwaysOnTop'].setChecked(mc.optionVar(q='GSCT_' + 'gsAlwaysOnTopToggle')) + WIDGETS['cvDistanceColor'].setChecked(mc.optionVar(q='GSCT_' + 'gsCVDistanceColor')) + WIDGETS['hullDistanceColor'].setChecked(mc.optionVar(q='GSCT_' + 'gsHullDistanceColor')) + WIDGETS['curveDistanceColor'].setChecked(mc.optionVar(q='GSCT_' + 'gsCurveDistanceColor')) + WIDGETS['CVocclusion'].setChecked(mc.optionVar(q='GSCT_' + 'gsEnableCVOcclusion')) + # Load Floats + mc.floatSliderGrp('gsPointSizeSlider', e=1, v=(mc.optionVar(q='GSCT_' + 'gsPointSizeSlider'))) + mc.floatSliderGrp('gsCurveWidthSlider', e=1, v=(mc.optionVar(q='GSCT_' + 'gsCurveWidthSlider'))) + mc.floatSliderGrp('gsHullWidthSlider', e=1, v=(mc.optionVar(q='GSCT_' + 'gsHullWidthSlider'))) + WIDGETS['gsDeselectedCVAlpha'].setValue(mc.optionVar(q='GSCT_' + 'gsDeselectedCVAlpha')) + WIDGETS['gsSelectedCVAlpha'].setValue(mc.optionVar(q='GSCT_' + 'gsSelectedCVAlpha')) + WIDGETS['gsCurveHighlightAlpha'].setValue(mc.optionVar(q='GSCT_' + 'gsCurveHighlightAlpha')) + WIDGETS['gsHullHighlightAlpha'].setValue(mc.optionVar(q='GSCT_' + 'gsHullHighlightAlpha')) + WIDGETS['gsDistanceColorMinValue'].setValue(mc.optionVar(q='GSCT_' + 'gsDistanceColorMinValue')) + WIDGETS['gsDistanceColorMaxValue'].setValue(mc.optionVar(q='GSCT_' + 'gsDistanceColorMaxValue')) + # Load Colors + WIDGETS['gsDeselectedCVColor'].setRGBColors(eval(mc.optionVar(q='GSCT_' + 'gsDeselectedCVColor'))) + WIDGETS['gsSelectedCVColor'].setRGBColors(eval(mc.optionVar(q='GSCT_' + 'gsSelectedCVColor'))) + WIDGETS['gsCurveHighlightColor'].setRGBColors(eval(mc.optionVar(q='GSCT_' + 'gsCurveHighlightColor'))) + WIDGETS['gsHullHighlightColor'].setRGBColors(eval(mc.optionVar(q='GSCT_' + 'gsHullHighlightColor'))) + # String Values + WIDGETS['gsOccluderMeshName'].setText(mc.optionVar(q='GSCT_' + 'gsOccluderMeshName')) + self.applySettingsToNode() + + def saveSettingsFromUI(self): + """Save settings from UI to optionVars""" + mc.optionVar(fv=['GSCT_' + 'gsPointSizeSlider', mc.floatSliderGrp('gsPointSizeSlider', q=1, v=1)]) + mc.optionVar(fv=['GSCT_' + 'gsCurveWidthSlider', mc.floatSliderGrp('gsCurveWidthSlider', q=1, v=1)]) + mc.optionVar(fv=['GSCT_' + 'gsHullWidthSlider', mc.floatSliderGrp('gsHullWidthSlider', q=1, v=1)]) + mc.optionVar(sv=['GSCT_' + 'gsDeselectedCVColor', str(WIDGETS["gsDeselectedCVColor"].getRGBColors())]) + mc.optionVar(fv=['GSCT_' + 'gsDeselectedCVAlpha', WIDGETS['gsDeselectedCVAlpha'].getValue()]) + mc.optionVar(sv=['GSCT_' + 'gsSelectedCVColor', str(WIDGETS["gsSelectedCVColor"].getRGBColors())]) + mc.optionVar(fv=['GSCT_' + 'gsSelectedCVAlpha', WIDGETS['gsSelectedCVAlpha'].getValue()]) + mc.optionVar(sv=['GSCT_' + 'gsCurveHighlightColor', str(WIDGETS["gsCurveHighlightColor"].getRGBColors())]) + mc.optionVar(fv=['GSCT_' + 'gsCurveHighlightAlpha', WIDGETS['gsCurveHighlightAlpha'].getValue()]) + mc.optionVar(sv=['GSCT_' + 'gsHullHighlightColor', str(WIDGETS["gsHullHighlightColor"].getRGBColors())]) + mc.optionVar(fv=['GSCT_' + 'gsHullHighlightAlpha', WIDGETS['gsHullHighlightAlpha'].getValue()]) + mc.optionVar(iv=['GSCT_' + 'gsCurveVisibilityToggle', WIDGETS['curveVisibility'].isChecked()]) + mc.optionVar(iv=['GSCT_' + 'gsHullVisibilityToggle', WIDGETS['hullVisibility'].isChecked()]) + mc.optionVar(iv=['GSCT_' + 'gsLazyUpdateToggle', WIDGETS['lazyUpdate'].isChecked()]) + mc.optionVar(iv=['GSCT_' + 'gsAlwaysOnTopToggle', WIDGETS['alwaysOnTop'].isChecked()]) + mc.optionVar(iv=['GSCT_' + 'gsCVDistanceColor', WIDGETS['cvDistanceColor'].isChecked()]) + mc.optionVar(iv=['GSCT_' + 'gsHullDistanceColor', WIDGETS['hullDistanceColor'].isChecked()]) + mc.optionVar(iv=['GSCT_' + 'gsCurveDistanceColor', WIDGETS['curveDistanceColor'].isChecked()]) + mc.optionVar(fv=['GSCT_' + 'gsDistanceColorMinValue', WIDGETS['gsDistanceColorMinValue'].getValue()]) + mc.optionVar(fv=['GSCT_' + 'gsDistanceColorMaxValue', WIDGETS['gsDistanceColorMaxValue'].getValue()]) + mc.optionVar(iv=['GSCT_' + 'gsEnableCVOcclusion', WIDGETS['CVocclusion'].isChecked()]) + mc.optionVar(sv=['GSCT_' + 'gsOccluderMeshName', str(WIDGETS["gsOccluderMeshName"].text())]) + + def applySettingsToNode(self): + """Applies user settings from the UI to created node. A bit slow, but works for this.""" + if not all(mc.getClassification("GSCT_CurveTools_DrawManagerNode")): # Check if node type exists + return + nodes = mc.ls(typ='GSCT_CurveTools_DrawManagerNode') + for node in nodes: + if mc.referenceQuery(node, inr=1): + continue + mc.setAttr(node + '.pointSize', float(mc.floatSliderGrp('gsPointSizeSlider', q=1, v=1))) + mc.setAttr(node + '.lineWidth', float(mc.floatSliderGrp('gsCurveWidthSlider', q=1, v=1))) + mc.setAttr(node + '.hullWidth', float(mc.floatSliderGrp('gsHullWidthSlider', q=1, v=1))) + dpc_r, dpc_g, dpc_b = WIDGETS["gsDeselectedCVColor"].getRGBColors() + mc.setAttr(node + '.deselectedPointColor', dpc_r, dpc_g, dpc_b, typ='double3') + mc.setAttr(node + '.deselectedPointAlpha', WIDGETS['gsDeselectedCVAlpha'].getValue()) + spc_r, spc_g, spc_b = WIDGETS["gsSelectedCVColor"].getRGBColors() + mc.setAttr(node + '.selectedPointColor', spc_r, spc_g, spc_b, typ='double3') + mc.setAttr(node + '.selectedPointAlpha', WIDGETS['gsSelectedCVAlpha'].getValue()) + crvc_r, crvc_g, crvc_b = WIDGETS["gsCurveHighlightColor"].getRGBColors() + mc.setAttr(node + '.curveColor', crvc_r, crvc_g, crvc_b, typ='double3') + mc.setAttr(node + '.curveAlpha', WIDGETS['gsCurveHighlightAlpha'].getValue()) + hc_r, hc_g, hc_b = WIDGETS["gsHullHighlightColor"].getRGBColors() + mc.setAttr(node + '.hullColor', hc_r, hc_g, hc_b, typ='double3') + mc.setAttr(node + '.hullAlpha', WIDGETS['gsHullHighlightAlpha'].getValue()) + mc.setAttr(node + '.showCurve', WIDGETS['curveVisibility'].isChecked()) + mc.setAttr(node + '.showHull', WIDGETS['hullVisibility'].isChecked()) + mc.setAttr(node + '.lazyUpdate', WIDGETS['lazyUpdate'].isChecked()) + mc.setAttr(node + '.drawOnTop', WIDGETS['alwaysOnTop'].isChecked()) + mc.setAttr(node + '.useCVDistanceColor', WIDGETS['cvDistanceColor'].isChecked()) + mc.setAttr(node + '.useHullDistanceColor', WIDGETS['hullDistanceColor'].isChecked()) + mc.setAttr(node + '.useCurveDistanceColor', WIDGETS['curveDistanceColor'].isChecked()) + mc.setAttr(node + '.distanceColorMin', WIDGETS['gsDistanceColorMinValue'].getValue()) + mc.setAttr(node + '.distanceColorMax', WIDGETS['gsDistanceColorMaxValue'].getValue()) + mc.setAttr(node + '.useCVOcclusion', WIDGETS['CVocclusion'].isChecked()) + mc.setAttr(node + '.occluderMeshName', WIDGETS['gsOccluderMeshName'].text(), typ='string') + + def selectOccluderFromScene(self): + mesh = mc.ls(sl=1, tr=1) + if not mesh: + return + for obj in mesh: + if mc.nodeType(mc.listRelatives(obj, c=1, pa=1)) != "mesh": + continue + WIDGETS['gsOccluderMeshName'].setText(obj) + break + else: + MESSAGE.warningInView("No compatible meshes selected. Select a mesh object.") + self.applySettingsToNode() + + +advancedVisibility = AdvancedVisibility("advancedVisibility") + + +### Layers, Interface and Other Updates ### + +def updateMainUI(clearLayerCollections=False): + """Updates main UI controls""" + + # --- Change Layer number --- + num = 20 + if 'layerRowsActionGroup' in WIDGETS: + checkbox = WIDGETS['layerRowsActionGroup'].checkedAction().objName + else: + LOGGER.warning("'layerRowsActionGroup' not be found") + return + checkboxNum = re.findall(r'\d+', checkbox)[0] + num = int(checkboxNum) * 10 + active = set(range(int(checkboxNum))) + allLayers = set(range(8)) + for a in active: + if 'layerRow%s' % a in WIDGETS: + WIDGETS['layerRow%s' % a].setHidden(False) + for a in (allLayers - active): + WIDGETS['layerRow%s' % a].setHidden(True) + WIDGETS['curveGrp0'].setNumberOfLayers(num) + WIDGETS['LayerLayout'].update() + WIDGETS['LayerLayout'].parentWidget().update() + + # --- Update Layer Collections --- + comboBox = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + if clearLayerCollections: + comboBox.clear() + if mc.objExists('defaultLayer') and mc.attributeQuery('gsCollectionsDict', n='defaultLayer', ex=1): + collectionsString = mc.getAttr('defaultLayer.gsCollectionsDict') + collectionsDict = dict(eval(collectionsString)) + for key in collectionsDict: + if comboBox.findText(collectionsDict[key]) == -1: + comboBox.insertItem(key, collectionsDict[key]) + if WIDGETS['layerCollectionsComboBox'].currentIndex() == 0: + WIDGETS['layerCollectionsMinus'].setEnabled(False) + else: + WIDGETS['layerCollectionsMinus'].setEnabled(True) + updateVisibilityBasedOnActiveCollection() + + # --- Update layer buttons --- + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + for layerID in range(80): + button = 'curveGrp%s' % layerID + buttonWidget = WIDGETS[button] + grpCurve, grpGeo, grpInst = utils.getFormattedLayerNames(collectionID, layerID) + if not mc.objExists(grpCurve): + buttonWidget.setStyle('empty') + continue + layers = mc.editDisplayLayerMembers(grpCurve, q=1, fn=1) + if mc.objExists(grpCurve) and mc.objExists(grpGeo) and mc.objExists(grpInst) and layers: + curve = mc.getAttr(grpCurve + '.visibility') + geo = mc.getAttr(grpGeo + '.visibility') + geoEdit = utils.getAttr(grpGeo, 'displayType') + if not curve and not geo: + buttonWidget.setStyle('hidden') # Hidden + elif (curve and geo and not geoEdit) or (not curve and geo and not geoEdit): + buttonWidget.setStyle('edit') # Active + Editable + elif curve and geo: + buttonWidget.setStyle('active') # Active + elif curve and not geo: + buttonWidget.setStyle('curve') # Curve only + elif geo and not curve: + buttonWidget.setStyle('geo') # Geo only + else: + buttonWidget.setStyle('empty') + else: + buttonWidget.setStyle('empty') + + # --- Update color mode button --- + if utils.getAttr('gsColorShaderStorageNode', 'colorApplied'): + WIDGETS['colorMode'].setChecked(True) + else: + WIDGETS['colorMode'].setChecked(False) + + # --- Update scale factor window --- + if mc.workspaceControl(SCALE_FACTOR_UI, q=1, ex=1) and \ + mc.workspaceControl(SCALE_FACTOR_UI, q=1, vis=1): + sel = mc.ls(sl=1, tr=1, typ='nurbsCurve') + try: + if sel and mc.attributeQuery('scaleFactor', n=sel[-1], ex=1): + scaleFactor = mc.getAttr(sel[-1] + '.scaleFactor') + if scaleFactor and scaleFactor >= 0.001: + WIDGETS['scaleFactorSelectedValue'].setText(str(scaleFactor)) + else: + WIDGETS['scaleFactorSelectedValue'].setText(str('####')) + except BaseException: + if 'scaleFactorSelectedValue' in WIDGETS: + WIDGETS['scaleFactorSelectedValue'].setText(str('####')) + + +def onSceneOpenedUpdateLayerCount(): + # Check max layer ID present in the scene + allLayers = mc.ls(typ="displayLayer") + filtered = [x for x in allLayers if 'curveGrp_' in x] + if not filtered: + return + layersIDs = [] + for layer in filtered: + split = layer.split("_") + try: + layerID = int(split[1]) if len(split) == 3 else int(split[2]) + except ValueError: + error = 'Failed to extract layer ID from layer "{}". Display Layer name is corrupted. Please delete corrupted layers and curves.'.format(layer) + MESSAGE.warning(error) + continue + layersIDs.append(layerID) + maxLayerID = max(layersIDs) + # Check currently active layer count on the interface + checkbox = WIDGETS['layerRowsActionGroup'].checkedAction().objName + checkboxNum = re.findall(r'\d+', checkbox)[0] + activeID = int(checkboxNum) * 10 + # Check the required layer count + availableIDs = [20, 30, 40, 60, 80] + availableIDs.append(maxLayerID + 1) + availableIDs.sort() + requiredID = availableIDs.index(maxLayerID + 1) + try: + requiredLayerNumber = availableIDs[requiredID + 1] + except Exception as e: + LOGGER.exception(e) + return + if activeID >= requiredLayerNumber: + return + WIDGETS['curveGrp0'].setNumberOfLayers(requiredLayerNumber) + formatControlName = str(requiredLayerNumber)[:1] + "layerRows" + WIDGETS[formatControlName].setChecked(True) + updateMainUI() + + +def saveOptions(): # Save options to optionVar + LOGGER.info("Saving options") + qtBooleans = [ + 'syncCurveColor', + 'colorizedRegroup', + + 'checkerPattern', + 'ignoreLastLayer', + 'syncOutlinerLayerVis', + 'keepCurveAttributes', + 'massBindOption', + 'boundCurvesFollowParent', + + 'bindDuplicatesCurves', + 'bindFlipUVs', + 'replacingCurveLayerSelection', + 'populateBlendAttributes', + 'useAutoRefineOnNewCurves', + 'flipUVsAfterMirror', + 'convertInstances', + + 'layerNumbersOnly', + '2layerRows', + '3layerRows', + '4layerRows', + '6layerRows', + '8layerRows', + 'warpSwitch', + 'enableTooltips', + 'colorOnlyDiffuse', + + 'showLayerCollectionsMenu', + 'importIntoANewCollection', + 'ignoreTemplateCollections', + 'groupTemplateCollections', + ] + for option in qtBooleans: + mc.optionVar(iv=['GSCT_' + option, WIDGETS[option].isChecked()]) + + # Editor colors + if 'UVEditorBGColorPicker' in WIDGETS: + mc.optionVar(sv=['GSCT_UVEditorBGColor', + str(utils.colorFrom1to255(WIDGETS['UVEditorBGColorPicker'].getRGBColors()))]) + mc.optionVar(sv=['GSCT_UVEditorGridColor', + str(utils.colorFrom1to255(WIDGETS['UVEditorGridColorPicker'].getRGBColors()))]) + mc.optionVar(sv=['GSCT_UVEditorFrameColor', + str(utils.colorFrom1to255(WIDGETS['UVEditorFrameColorPicker'].getRGBColors()))]) + mc.optionVar(sv=['GSCT_UVEditorUVCardFillColor', + str(utils.colorFrom1to255(WIDGETS['UVEditorUVCardFillColorPicker'].getRGBColors()))]) + mc.optionVar(sv=['GSCT_UVEditorUVFrameSelectedColor', + str(utils.colorFrom1to255(WIDGETS['UVEditorUVFrameSelectedColorPicker'].getRGBColors()))]) + mc.optionVar(sv=['GSCT_UVEditorUVFrameDeselectedColor', + str(utils.colorFrom1to255(WIDGETS['UVEditorUVFrameDeselectedColorPicker'].getRGBColors()))]) + + +def saveScaleFactor(windowName, deleteUI=True): + sliderValue = mc.floatSliderGrp('GSCT_scaleFactorSlider', q=1, v=1) + mc.optionVar(fv=('GSCT_globalScaleFactor', sliderValue)) + + # Check if scaleFactor node and scale factor attribute exists + if not mc.objExists('gsScaleFactorStorageNode'): + mc.scriptNode(n='gsScaleFactorStorageNode') + if not mc.attributeQuery('scaleFactor', n='gsScaleFactorStorageNode', ex=1): + mc.addAttr('gsScaleFactorStorageNode', ln='scaleFactor', at='double') + + mc.setAttr('gsScaleFactorStorageNode.scaleFactor', sliderValue) + if deleteUI: + mc.deleteUI(windowName) + + +def getScaleFactor(*_): + scaleFactor = 1.0 + if mc.objExists('gsScaleFactorStorageNode') and mc.attributeQuery('scaleFactor', n='gsScaleFactorStorageNode', ex=1): + scaleFactor = mc.getAttr('gsScaleFactorStorageNode.scaleFactor') + else: + scaleFactor = mc.optionVar(q=('GSCT_globalScaleFactor')) + if not scaleFactor or scaleFactor < 0.001: + MESSAGE.warning("Invalid scale factor. Reverting to default (1.0).") + scaleFactor = 1.0 + return scaleFactor + + +def getOption(name): + return mc.optionVar(q='GSCT_' + name) + + +def updateLayerThickness(): + """Updates thickness values for all Curves""" + saveOptions() + value = mc.optionVar(q='GSCT_globalCurveThickness') + layerCollectionsWidget = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + collectionCount = layerCollectionsWidget.count() + for collectionID in range(collectionCount): + collection = "%s_" % collectionID if collectionID > 0 else "" + for layerID in range(80): + grpCurve = 'curveGrp_%s%s_Curve' % (collection, layerID) + if not mc.objExists(grpCurve): + continue + curves = mc.editDisplayLayerMembers(grpCurve, q=1, fn=1) + if curves: + for curve in curves: + shape = mc.ls(curve, dag=1, s=1, l=1)[0] + mc.setAttr(shape + '.lineWidth', value) + curveControlUI.updateUI() + + +def setCurveThickness(curves): + """Sets curve thickness for provided curves""" + if not isinstance(curves, list): + curves = [curves] + value = mc.optionVar(q='GSCT_globalCurveThickness') + for crv in curves: + if isinstance(crv, tuple): + origShape = mc.ls(crv[0], dag=1, s=1)[0] + targetShape = mc.ls(crv[1], dag=1, s=1)[0] + mc.setAttr(targetShape + '.lineWidth', mc.getAttr(origShape + '.lineWidth')) + else: + shape = mc.ls(crv, dag=1, s=1)[0] + mc.setAttr(shape + '.lineWidth', value) + + +def layerClicked(i): + """Called when layer button is clicked""" + mod = utils.getMod() + if mod == 'Alt': + toggleLayerVisibility(i) + updateMainUI() + elif mod == 'Ctrl+Alt': + toggleObjVisibility(i, 1) + updateMainUI() + elif mod == 'Shift+Ctrl': + toggleObjVisibility(i, 0) + updateMainUI() + elif mod == 'Shift+Alt': + layersFilterToggle(False, False, "Ctrl") + toggleLayerVisibility(i) + updateMainUI() + elif mod == 'Shift+Ctrl+Alt': + alwaysOnTopToggleLayer(i) + else: + curveLayerSelectObj(i, -1) + + +def updateScaleFactorWindow(): + if not mc.workspaceControl(SCALE_FACTOR_UI, q=1, ex=1): + return + if not mc.workspaceControl(SCALE_FACTOR_UI, q=1, vis=1): + return + + sel = mc.ls(sl=1, tr=1, typ='nurbsCurve') + try: + if sel and mc.attributeQuery('scaleFactor', n=sel[-1], ex=1): + scaleFactor = mc.getAttr(sel[-1] + '.scaleFactor') + if scaleFactor and scaleFactor >= 0.001: + WIDGETS['scaleFactorSelectedValue'].setText(str(scaleFactor)) + else: + WIDGETS['scaleFactorSelectedValue'].setText(str('####')) + except BaseException: + if 'scaleFactorSelectedValue' in WIDGETS: + WIDGETS['scaleFactorSelectedValue'].setText(str('####')) + + +def clearLayers(collection=None): + # type: (str|int) -> None + """Removes unused layers in selected collection""" + c = '' + if collection: + c = '%s_' % collection + for i in range(80): + grpCurve = 'curveGrp_%s%s_Curve' % (c, i) + grpGeo = 'curveGrp_%s%s_Geo' % (c, i) + grpInst = 'curveGrp_%s%s_Inst' % (c, i) + if mc.objExists(grpCurve) and mc.objExists(grpGeo) and mc.objExists(grpInst): + curves = mc.editDisplayLayerMembers(grpCurve, q=1, fn=1) + geo = mc.editDisplayLayerMembers(grpGeo, q=1, fn=1) + instances = mc.editDisplayLayerMembers(grpInst, q=1, fn=1) + if not curves or not geo or not instances: + mc.delete(grpCurve) + mc.delete(grpGeo) + mc.delete(grpInst) + else: + if mc.objExists(grpCurve): + mc.delete(grpCurve) + if mc.objExists(grpGeo): + mc.delete(grpGeo) + if mc.objExists(grpInst): + mc.delete(grpInst) + + +def deleteUnusedLayers(checkButtons=True): + # type: (bool) -> None + """Removes unused layers in all collections""" + + collections = utils.getCollectionsSet() + for i in collections: + clearLayers(i) + clearLayers() + if checkButtons: + updateMainUI() + + +def resetSlider(): # Reset Rebuild Curve Slider to default values + mc.intSliderGrp('gsRebuildSlider', e=1, min=1, max=50, fmx=999, v=1) + + +def resetControlSliders(): # Reset Curve Control window Sliders to default values + sliders = curveControlUI.allControls + for control in sliders: + if control in WIDGETS: + slider = WIDGETS[control] + if "resetMinMax" in dir(slider) and slider.isVisible(): + slider.resetMinMax() + + +def resetWarpCurvesControls(curve): # Reset curve control falloffCurveAttr + if curve == 1: + mc.falloffCurveAttr('gsCurveControlTwistCurve', e=1, asString="0, 0.5, 0.333, 0.5, 0.667, 0.5, 1, 0.5") + elif curve == 2: + mc.falloffCurveAttr('gsCurveControlWidthFalloff', e=1, asString="0, 0.5, 0.333, 0.5, 0.667, 0.5, 1, 0.5") + + +def curveControlCheckBoxes(box): # Updates check boxes in Curve Control window + sel = mc.filterExpand(mc.ls(sl=1, fl=1, o=1), sm=9) + if not sel: + curveControlUI.updateUI() + return 0 + for obj in sel: + if box == 0: + if utils.attrExists(obj, 'reverseNormals'): + mc.setAttr(obj + '.reverseNormals', not WIDGETS['reverseNormals'].isChecked()) + elif box == 1: + if utils.attrExists(obj, 'reverseNormals'): + mc.setAttr(obj + '.solidify', WIDGETS['solidify'].isChecked()) + elif box == 2: + if utils.attrExists(obj, 'LengthLock'): + mc.setAttr(obj + '.LengthLock', WIDGETS['LengthLock'].isChecked()) + elif box == 3: + if utils.attrExists(obj, 'flipUV'): + mc.setAttr(obj + '.flipUV', not WIDGETS['flipUV'].isChecked()) + + +def layersFilterToggle(curve, geo, mod=None, hotkey=False, ignore=None): + # type: (bool, bool, str|None, bool, None|list[str]) -> None + """ + Toggles the visibility of the components for all layers (and optionally collections) + mod - allows to manually pass "Ctrl, Shift etc" hotkey + hotkey - indicates that the call is a hotkey and should ignore user modifier keys + ignore - list of ignored hotkey combinations + """ + + if mod is None and not hotkey: + mod = utils.getMod() + + # Handle ignored hotkeys + if ignore and mod and mod in ignore: # TODO: Fix this hotkey-ignore-mod madness + mod = None + + if mod and (mod in ["Shift", "Shift+Ctrl"]): + curve = False + geo = False + + # Handle the hotkey state + allCollections = False + if mod and "Ctrl" in mod: + allCollections = True + + # Handle last layer ignore + ignoreLastLayer = bool(getOption('ignoreLastLayer')) + + # Sync visibility with the outliner + outlinerSync = getOption('syncOutlinerLayerVis') + + # Check available collections + collectionIDs = [] + if allCollections: + collectionIDs.append('0') + collectionIDs += list(utils.getCollectionsSet()) + else: + collectionIDs.append(WIDGETS['layerCollectionsComboBox'].currentIndex()) + + # Check for Template Ignore + if getOption('ignoreTemplateCollections'): + collectionsWidget = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + currentCollection = collectionsWidget.currentIndex() + collectionIDs_copy = list(collectionIDs) + for collection in collectionIDs_copy: + if "template" in collectionsWidget.itemText(int(collection)).lower() and int(collection) != currentCollection: + collectionIDs.remove(collection) + + for collection in collectionIDs: + formattedCollectionID = utils.getFormattedCollectionByID(collection) + currentLayerNum = WIDGETS['curveGrp0'].LAYERS - 1 + for i in range(80): + if ignoreLastLayer and i == currentLayerNum: + continue + curvesGrp = 'curveGrp_%s%s_Curve' % (formattedCollectionID, i) + geoGrp = 'curveGrp_%s%s_Geo' % (formattedCollectionID, i) + instGrp = 'curveGrp_%s%s_Inst' % (formattedCollectionID, i) + try: + mc.setAttr(curvesGrp + '.visibility', curve) + if mc.layerButton(curvesGrp, q=1, ex=1): + mc.layerButton(curvesGrp, e=1, lv=curve) + except BaseException: + pass + try: + mc.setAttr(geoGrp + '.visibility', geo) + if mc.layerButton(geoGrp, q=1, ex=1): + mc.layerButton(geoGrp, e=1, lv=geo) + except BaseException: + pass + try: + mc.setAttr(instGrp + '.visibility', 0) + if mc.layerButton(instGrp, q=1, ex=1): + mc.layerButton(instGrp, e=1, lv=0) + except BaseException: + pass + if outlinerSync and mc.objExists(curvesGrp): + groups = [] + layerCurves = mc.editDisplayLayerMembers(curvesGrp, q=1, fn=1) + if layerCurves: + for i in layerCurves: + parent = mc.listRelatives(i, p=1, pa=1) + if parent: + groups += parent + if not geo and not curve: + for grp in groups: + mc.setAttr(grp + '.v', 0) + else: + for grp in groups: + mc.setAttr(grp + '.v', 1) + updateMainUI() + + +def applyAxis(axis): + sel = mc.filterExpand(mc.ls(sl=1, o=1), sm=9) + if not sel: + return 0 + for curve in sel: + if axis == -1: + if mc.attributeQuery('AxisFlip', n=curve, ex=1): + mc.setAttr(curve + '.AxisFlip', not mc.getAttr(curve + '.AxisFlip')) + if mc.attributeQuery('reverseNormals', n=curve, ex=1): + mc.setAttr(curve + '.reverseNormals', not mc.getAttr(curve + '.reverseNormals')) + curveControlUI.updateUI() + elif axis == 0: + if mc.attributeQuery('Axis', n=curve, ex=1): + mc.setAttr(curve + '.Axis', 0) + elif axis == 1: + if mc.attributeQuery('Axis', n=curve, ex=1): + mc.setAttr(curve + '.Axis', 1) + elif axis == 2: + if mc.attributeQuery('Axis', n=curve, ex=1): + mc.setAttr(curve + '.Axis', 2) + elif axis == 3: + if mc.attributeQuery('Axis', n=curve, ex=1): + mc.setAttr(curve + '.Axis', 3) + + +def changeLayerViaOptionMenu(): + layer = WIDGETS['gsLayerSelector'].currentIndex() + curveAddToLayer(layer) + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + + +def moveLayers(source, target): + # type: (str, str) -> None + """Moves the curves from layer to layer within the same collection (via MMB drag)""" + mc.undoInfo(ock=1, cn='layerDragDropOP') + try: + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + collection = '' + if collectionID: + collection = '%s_' % collectionID + sourceGrp = re.findall(r'\d+', source)[0] + targetGrp = re.findall(r'\d+', target)[0] + curves = mc.editDisplayLayerMembers('curveGrp_%s%s_Curve' % (collection, sourceGrp), q=1, fn=1) + if utils.getMod() == 'Shift': + customSel = mc.editDisplayLayerMembers('curveGrp_%s%s_Curve' % (collection, sourceGrp), q=1, fn=1) + mc.select(customSel, r=1) + duplicateCurve() + curveAddToLayer(targetGrp) + else: + curveAddToLayer(targetGrp, sourceGrp) + if curves: + if mc.objExists('gsColorShaderStorageNode'): + if mc.getAttr('gsColorShaderStorageNode.colorApplied'): + toggleColor.updateColors() + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + except BaseException: + pass + mc.undoInfo(cck=1) + + +### General Functions ### + +def subdivideCurve(hk=None): + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, o=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one compatible card or tube curve.') + return + finalCurves = [] + for obj in sel: + if mc.attributeQuery('Axis', n=obj, ex=1): + MESSAGE.warning('%s is not compatible with Subdivide Curve command. Skipped.' % obj) + continue + geo = selectPart(2, True, obj) + geo = mc.duplicate(geo) + mc.sets(geo, forceElement='initialShadingGroup', nw=1) # set to default material to avoid errors + subd = mc.polyToSubdiv(geo, ch=0) + surface = mc.subdToNurbs(subd, ch=0) + nurbs = mc.listRelatives(surface, c=1, pa=1) + nurbs = mc.parent(nurbs, w=1) + mc.rebuildSurface(nurbs, ch=0, rpo=1, rt=6, end=1, kr=1, kcp=0, kc=0, su=16, du=0, sv=5, dv=0, tol=0.01, fr=0, + dir=2) + mc.delete(geo, surface, subd) + spansU, spansV = mc.getAttr(nurbs[0] + '.spansUV')[0] + span = 'v' if spansU > spansV else 'u' + attrs = attributes.getAttr(obj) + curves = [] + num = mc.intSliderGrp('gsCurvesSlider', q=1, v=1) + for i in range(0, num): + x = (float(i) + 1.0) / (float(num) + 1.0) + if 'WidthX' in attrs: + x = float(i) / float(num) + curves.append(mc.duplicateCurve(nurbs[0] + '.%sn[%s]' % (span, x))[0]) + widthValues = {1: 1, 2: 0.666, 3: 0.5, 4: 0.4, 5: 0.333, 6: 0.28, 7: 0.24, 8: 0.213, 9: 0.19, 10: 0.166} + if 'Width' in attrs: + attrs['Width'] = attrs['Width'] * widthValues[num] + attrs['Profile'] = attrs['Profile'] / num + elif 'WidthX' in attrs: + attrs['WidthX'] = attrs['WidthX'] * widthValues[num] + attrs['WidthZ'] = attrs['WidthZ'] * widthValues[num] + mc.delete(nurbs) + smoothCurve(curves) + for curve in curves: + mc.rebuildCurve(curve, kr=2, s=mc.getAttr(obj + '.spans')) + if 'Profile' in attrs: + mc.reverseCurve(curve) + targetPoints = mc.getAttr(curve + '.cp[:]') + newCurve = duplicateCurve(customSel=[obj])[0] + finalCurves.append(newCurve) + for p in range(0, len(targetPoints)): + mc.xform('%s.cp[%s]' % (newCurve, p), t=targetPoints[p], wd=1, ws=1) + attributes.setAttr(newCurve, attrs) + mc.delete(curve) + modifier = True if hk == 2 or (hk is None and utils.getMod() == 'Shift') else False + if not modifier: + mc.delete(selectPart(0, True, obj)) + mc.select(finalCurves) + if finalCurves: + resetCurvePivotPoint() + + +def cardToCurve(): + """Converts selected geo card to curve or (optionally) Curve Card""" + sel = mc.filterExpand(mc.ls(sl=1, tr=1, fl=1), sm=12) + if not sel: + sel = mc.filterExpand(mc.ls(hl=1, fl=1), sm=12) + if not sel: + MESSAGE.warningInView('Select a compatible one sided geometry to convert.') + return + finalSel = [] + progress = utils.ProgressBar("Converting cards to curves...", len(sel)) + options = eval(mc.optionVar(q='GSCT_CardToCurveOptions')) + + def getConvertOption(name): + return options[name] if name in options else True + + currentUnit = mc.currentUnit(q=1, linear=1) + if currentUnit != "cm" and getConvertOption('gsCardToCurve_profile'): + mc.confirmDialog( + title="Unit Warning", + message="Card to Curve 'Profile' matching is only supported with centimeters (cm) as scene units.\n\n" + + "You are using '{}'.\n\n".format(currentUnit) + + "Disable Profile Attribute matching or switch to centimiters in Maya Settings." + ) + progress.end() + return + + for obj in sel: + if progress.tick(1): + break + allFaces = "{}.f[*]".format(obj) + perimeterEdges = mc.polyListComponentConversion(allFaces, ff=1, te=1, bo=1) + perimeterVerts = mc.ls(mc.polyListComponentConversion(perimeterEdges, fe=1, tv=1), fl=1) + cornerVerts = [] + for vert in perimeterVerts: + expandedEdge = mc.ls(mc.polyListComponentConversion(vert, fv=1, te=1), fl=1) + if len(expandedEdge) == 2: + cornerVerts.append(vert) + + if not cornerVerts and len(cornerVerts) != 4: + MESSAGE.warningInView('Object "{}" is not a one-sided card. Skipping.'.format(obj)) + continue + + # Find closest point to cornerVerts[0] + closestPoint = None + om_distance = 0 + om_cornerVert = om.MFloatPoint(mc.pointPosition(cornerVerts[0], w=1)) + for i in range(1, len(cornerVerts)): + om_otherVert = om.MFloatPoint(mc.pointPosition(cornerVerts[i], w=1)) + om_distance_new = om_cornerVert.distanceTo(om_otherVert) + if i == 1 or om_distance_new < om_distance: + om_distance = om_distance_new + closestPoint = cornerVerts[i] + + # Closest corner verts + firstCornerVertPair = [cornerVerts[0], closestPoint] + secondCornerVertPair = list(set(cornerVerts) - set(firstCornerVertPair)) + + origFirstCornerDistance = om_cornerVert.distanceTo(om.MFloatPoint(mc.pointPosition(closestPoint, w=1))) + origSecondCornerDistance = om.MFloatPoint( + mc.pointPosition(secondCornerVertPair[0], w=1)).distanceTo(om.MFloatPoint(mc.pointPosition(secondCornerVertPair[1], w=1))) + + if len(firstCornerVertPair) != 2 or len(secondCornerVertPair) != 2: + MESSAGE.warningInView('Object "{}" has wrong topology. Skipped.'.format(obj)) + continue + + # Verts between closest corners + firstSideVertLoop = utils.polySelectSp(firstCornerVertPair, obj) + secondSideVertLoop = utils.polySelectSp(secondCornerVertPair, obj) + + # Edges between closest corners if there are no width spans + firstSideEdgeLoop = mc.ls(mc.polyListComponentConversion(firstCornerVertPair, fv=1, te=1, internal=1), fl=1) + secondSideEdgeLoop = mc.ls(mc.polyListComponentConversion(secondCornerVertPair, fv=1, te=1, internal=1), fl=1) + + # Edges between closest corners if there ARE width spans + if not firstSideEdgeLoop: + firstSideEdgeLoop = mc.ls(mc.polyListComponentConversion(firstSideVertLoop, fv=1, te=1, internal=1), fl=1) + if not secondSideEdgeLoop: + secondSideEdgeLoop = mc.ls(mc.polyListComponentConversion(secondSideVertLoop, fv=1, te=1, internal=1), fl=1) + + # Find the first long edge + # TODO: See if there is a need for those test loops + testVertLoop1 = utils.polySelectSp([firstCornerVertPair[0], secondCornerVertPair[0]], obj) + testVertLoop2 = utils.polySelectSp([firstCornerVertPair[0], secondCornerVertPair[1]], obj) + firstLongEdge = mc.ls(mc.polyListComponentConversion( + min(testVertLoop1, testVertLoop2, key=lambda v: len(v)), fv=1, te=1, internal=1), fl=1) + + # Open Edges + edges = mc.ls(mc.polyListComponentConversion(firstCornerVertPair[0], fv=1, te=1), fl=1) + openEdge = mc.ls(mc.polySelectSp(edges, q=1, loop=1), fl=1) + + # Second long edge + secondLongEdge = list(set(openEdge) - set(firstLongEdge + firstSideEdgeLoop + secondSideEdgeLoop)) + + # Select the longest edges and convert them + mc.select(firstLongEdge + secondLongEdge, r=1) + edgeToCurve(enableProgressBar=False) + curves = mc.ls(sl=1, tr=1) + pathCurve = None + if len(curves) >= 2: + loft = mc.loft(curves, ss=2) + finalCurve = mc.duplicateCurve(loft[0] + '.u[0.5]', ch=0) + finalCurve = mc.rename(finalCurve[0], 'convertedCurve#') + mc.delete(curves, loft) + pathCurve = finalCurve + + # ------------- Reversing ------------- + if getConvertOption('gsCardToCurve_reverseCurve'): + pathCurve = mc.reverseCurve(pathCurve, ch=0, rpo=1)[0] + + """Converting to cards, orienting and adjusting to the original (optional)""" + if not mc.optionVar(q='GSCT_CardToCurveOutputType'): + # ------------- Converting ------------- + mc.select(pathCurve, r=1) + if not mc.optionVar(q='GSCT_CardToCurveCardType'): # Warp + pathCurve = create.multiple(0, hk=True, progressBar=False, keepAttrs=False)[0] + else: # Extrude + pathCurve = create.multiple(-2, hk=True, progressBar=False, keepAttrs=False)[0] + + if mc.attributeQuery('scaleFactor', n=pathCurve, ex=1): + scaleFactor = mc.getAttr(pathCurve + '.scaleFactor') + else: + scaleFactor = 1.0 + + newGeo = selectPart(2, True, pathCurve)[0] + om_sel = om.MSelectionList() + om_sel.add(newGeo) + om_mesh = om.MFnMesh(om_sel.getDagPath(0)) + """Adjust the resulting card to closely match the target geo""" + # ------------- Orienting ------------- + firstCv = '%s.cv[0]' % pathCurve + if getConvertOption('gsCardToCurve_orientation'): + pos = mc.pointPosition(firstCv) + om_pos = om.MPoint(pos) + targetClosestPointNormal = utils.getClosestPointAndNormal(obj, pos)[1] + orientation = pathCurve + '.Orientation' + prevDiff = 360 + currentDiff = 360 + i = 0 + guesses = [] + while (currentDiff >= 1) and (i <= 5): + i += 1 + currentOrien = mc.getAttr(orientation) + om_cardFaceNormal = om_mesh.getClosestPointAndNormal(om_pos, space=om.MSpace.kWorld)[1] + currentDiff = om_cardFaceNormal.angle(targetClosestPointNormal) * 180 / math.pi % 360 + guesses.append((currentDiff, currentOrien)) + if currentDiff > prevDiff: + guess = currentOrien - currentDiff + else: + guess = currentOrien + currentDiff + prevDiff = currentDiff + nextOrien = guess + mc.setAttr(orientation, nextOrien % 360) + # Setting the best guess as the final orientation + finalOrientation = min(guesses, key=lambda t: t[0])[1] + mc.setAttr(orientation, finalOrientation) + + # ------------- Width ------------- + # BUG: Fix width calculation to be based on the widest part of the card, not on some average value + if getConvertOption('gsCardToCurve_width'): + allFaces = "{}.f[*]".format(newGeo) + perimeterEdges = mc.polyListComponentConversion(allFaces, ff=1, te=1, bo=1) + perimeterVerts = mc.ls(mc.polyListComponentConversion(perimeterEdges, fe=1, tv=1), fl=1) + cornerVerts = [] + for vert in perimeterVerts: + expandedEdge = mc.ls(mc.polyListComponentConversion(vert, fv=1, te=1), fl=1) + if len(expandedEdge) == 2: + cornerVerts.append(vert) + closestPoint = None + om_distance = 0 + om_cornerVert = om.MFloatPoint(mc.pointPosition(cornerVerts[0], w=1)) + for i in range(1, len(cornerVerts)): + om_otherVert = om.MFloatPoint(mc.pointPosition(cornerVerts[i], w=1)) + om_distance_new = om_cornerVert.distanceTo(om_otherVert) + if i == 1 or om_distance_new < om_distance: + om_distance = om_distance_new + closestPoint = cornerVerts[i] + # Closest corner verts + firstCornerVertPair = [cornerVerts[0], closestPoint] + secondCornerVertPair = list(set(cornerVerts) - set(firstCornerVertPair)) + newFirstCornerDistance = om_cornerVert.distanceTo(om.MFloatPoint(mc.pointPosition(closestPoint, w=1))) + newWidth = abs(origFirstCornerDistance / newFirstCornerDistance) + mc.setAttr(pathCurve + '.Width', newWidth) + # ------------- Taper ------------- + if getConvertOption('gsCardToCurve_taper'): + taper = origSecondCornerDistance / origFirstCornerDistance + mc.setAttr(pathCurve + '.Taper', taper) + + # ------------- Twist ------------- + lastCV = '%s.cv[%s]' % (pathCurve, mc.getAttr(pathCurve + '.spans') + 2) + if getConvertOption('gsCardToCurve_twist'): + pos = mc.pointPosition(lastCV) + om_pos = om.MPoint(pos) + targetClosestPointNormal = utils.getClosestPointAndNormal(obj, pos)[1] + twist = pathCurve + '.Twist' + prevDiff = 180 + currentDiff = 180 + i = 0 + guesses = [] + while (currentDiff >= 1) and (i <= 5): + i += 1 + currentTwist = mc.getAttr(twist) + om_cardFaceNormal = om_mesh.getClosestPointAndNormal(om_pos, space=om.MSpace.kWorld)[1] + currentDiff = om_cardFaceNormal.angle(targetClosestPointNormal) * 180 / math.pi + guesses.append((currentDiff, currentTwist)) + if currentDiff > prevDiff: + guess = currentTwist - currentDiff + else: + guess = currentTwist + currentDiff + prevDiff = currentDiff + nextTwist = guess + mc.setAttr(twist, nextTwist) + # Setting the best guess as the final orientation + finalTwist = min(guesses, key=lambda t: t[0])[1] + mc.setAttr(pathCurve + '.Twist', finalTwist) + + # ------------- Profile ------------- + # TODO: Possibly add profile curve matching for warp cards + if getConvertOption('gsCardToCurve_profile'): + # First iteration of profile + firstCVpos = mc.pointPosition(firstCv, w=1) + firstCVom_pos = om.MPoint(firstCVpos) + firstCVtargetClosestPoint = utils.getClosestPointAndNormal(obj, firstCVpos)[0] + firstProfileDistance = firstCVom_pos.distanceTo(firstCVtargetClosestPoint) + lastCVpos = mc.pointPosition(lastCV, w=1) + lastCVom_pos = om.MPoint(lastCVpos) + lastCVtargetClosestPoint = utils.getClosestPointAndNormal(obj, lastCVpos)[0] + lastProfileDistance = lastCVom_pos.distanceTo(lastCVtargetClosestPoint) + maxDist = max(firstProfileDistance, lastProfileDistance) + mc.setAttr(pathCurve + '.Profile', maxDist / scaleFactor * 1.4) + # Additional check in case the card was flipped in the wrong direction + firstCVtargetClosestPoint_2 = utils.getClosestPointAndNormal(newGeo, firstCVpos)[0] + lastCVtargetClosestPoint_2 = utils.getClosestPointAndNormal(newGeo, lastCVpos)[0] + firstTotalDistance = firstCVtargetClosestPoint.distanceTo(firstCVtargetClosestPoint_2) + lastTotalDistance = lastCVtargetClosestPoint.distanceTo(lastCVtargetClosestPoint_2) + if firstTotalDistance > firstProfileDistance and lastTotalDistance > lastProfileDistance: + mc.setAttr(pathCurve + '.Profile', mc.getAttr(pathCurve + '.Profile') * -1) + + # ------------- Material transfer ------------- + if getConvertOption('gsCardToCurve_material'): + mc.sets(newGeo, forceElement=list(utils.getShader(obj))[0]) + + # ------------- UV Approximation ------------- + if getConvertOption('gsCardToCurve_UVs'): + uVals, vVals = mc.polyEvaluate(obj, b2=True) + uMin, uMax = uVals + vMin, vMax = vVals + moveU = abs(uMax - uMin) / 2 + uMin - 0.5 + if getConvertOption('gsCardToCurve_verticalFlip'): + mc.setAttr(pathCurve + '.rotateUV', 180) + moveV = vMin + (vMax - vMin) - 1 + mc.setAttr(pathCurve + '.flipUV', not mc.getAttr(pathCurve + '.flipUV')) + else: + moveV = vMin + if getConvertOption('gsCardToCurve_horizontalFlip'): + mc.setAttr(pathCurve + '.flipUV', not mc.getAttr(pathCurve + '.flipUV')) + scaleU = abs(uMax - uMin) + scaleV = abs(vMax - vMin) + mc.setAttr(pathCurve + '.moveU', moveU) + mc.setAttr(pathCurve + '.moveV', moveV) + mc.setAttr(pathCurve + '.scaleU', scaleU) + mc.setAttr(pathCurve + '.scaleV', scaleV) + + if pathCurve: + finalSel.append(pathCurve) + progress.end() + mc.select(finalSel, r=1) + resetCurvePivotPoint() + + +def updateLattice(values, customTarget=None): + # TODO: Add curve fitting for better overall experience with the graph + + selection = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + + if not selection: + selection = mc.filterExpand(mc.ls(hl=1, o=1), sm=9) + if not selection and not customTarget: + return + if customTarget: + selection = [customTarget] + + for sel in selection: + if not mc.attributeQuery('latticeMessage', n=sel, ex=1): + continue + lattice = mc.listConnections(sel + '.latticeMessage') + if not lattice: + continue + else: + lattice = lattice[0] + + graphValues = utils.fromStringToDouble2(values) + + latticeDiv = mc.getAttr(lattice + '.sDivisions') + + if len(graphValues) != latticeDiv: + ffd = mc.listConnections(lattice + '.latticeOutput') + mc.lattice(ffd, e=1, rt=1) + mc.lattice(ffd, e=1, dv=[len(graphValues), 2, 2]) + latticeDiv = mc.getAttr(lattice + '.sDivisions') + curveControlUI.updateUI() + + currentPoints = [] + initialPoints = [] + for i in range(latticeDiv): + currentPoints.append(mc.pointPosition(lattice + '.pt[%s][1][0]' % (i), l=1)) + for i in range(mc.getAttr(sel + '.initialLatticePoints', s=1)): + initialPoints.append(mc.getAttr(sel + '.initialLatticePoints[%s]' % i)[0]) + + newPoints = [] + + for i in range(latticeDiv): + x = mt.lerp(graphValues[i][0], 0.5, -0.5, 1, 0) + y = mt.lerp(graphValues[i][1], 1.5, -0.5, 1, 0) + newPoints.append((x, y)) + + for i in range(len(newPoints)): + mc.move(newPoints[i][0], newPoints[i][1], lattice + '.pt[%s][1][0]' % i, xy=1, ls=1) + mc.move(newPoints[i][0], newPoints[i][1], lattice + '.pt[%s][1][1]' % i, xy=1, ls=1) + mc.move(newPoints[i][0], newPoints[i][1], lattice + '.pt[%s][0][0]' % i, x=1, ls=1) + mc.move(newPoints[i][0], newPoints[i][1], lattice + '.pt[%s][0][1]' % i, x=1, ls=1) + curveControlUI.updateUI() + + +def getLatticeValues(targetCurve): + if not mc.attributeQuery('latticeMessage', n=targetCurve, ex=1): + return + lattice = mc.listConnections(targetCurve + '.latticeMessage') + if lattice: + lattice = lattice[0] + else: + return + latticeDiv = mc.getAttr(lattice + '.sDivisions') + currentPoints = [] + initialPoints = [] + for i in range(latticeDiv): + currentPoints.append(mc.pointPosition(lattice + '.pt[%s][1][0]' % (i), l=1)) + for i in range(mc.getAttr(targetCurve + '.initialLatticePoints', s=1)): + initialPoints.append(mc.getAttr(targetCurve + '.initialLatticePoints[%s]' % i)[0]) + + newPoints = [] + + for i in range(latticeDiv): + x = mt.lerp(currentPoints[i][0], 1, 0, 0.5, -0.5) + y = mt.lerp(currentPoints[i][1], 1, 0, 1.5, -0.5) + newPoints.append((x, y)) + return newPoints + + +def equalizeProfileCurve(manual=False): + if WIDGETS['autoEqualizeSwitchOff'].isChecked() and not manual: + return + currentProfile = WIDGETS['profileCurve'].getGraph() + if currentProfile: + graphValues = utils.fromStringToDouble2(currentProfile) + for i in range(len(graphValues)): + graphValues[i][0] = float(i) / (len(graphValues) - 1) + newString = utils.fromDouble2ToString(graphValues) + WIDGETS['profileCurve'].setGraph(newString) + updateLattice(newString) + + +def resetProfileCurve(): + currentProfile = WIDGETS['profileCurve'].getGraph() + if currentProfile: + graphValues = utils.fromStringToDouble2(currentProfile) + for i in range(len(graphValues)): + graphValues[i][1] = 0.5 + newString = utils.fromDouble2ToString(graphValues) + WIDGETS['profileCurve'].setGraph(newString) + updateLattice(newString) + + +def transferProfileCurve(source, target): + if mc.attributeQuery('latticeMessage', n=target, ex=1) and mc.attributeQuery('latticeMessage', n=source, ex=1): + lattice = mc.listConnections(source + '.latticeMessage') + if lattice: + latticeDiv = mc.getAttr(lattice[0] + '.sDivisions') + if latticeDiv: + currentPoints = [] + if lattice: + for i in range(latticeDiv): + currentPoints.append(mc.pointPosition(lattice[0] + '.pt[%s][1][0]' % (i), l=1)) + newPoints = [] + for i in range(latticeDiv): + x = mt.lerp(currentPoints[i][0], 1, 0, 0.5, -0.5) + y = mt.lerp(currentPoints[i][1], 1, 0, 1.5, -0.5) + newPoints.append((x, y)) + + graphPoints = utils.fromDouble2ToString(newPoints) + + updateLattice(graphPoints, target) + + +def blendProfileCurve(source, target, ratio): + if mc.attributeQuery('latticeMessage', n=source, ex=1) and mc.attributeQuery('latticeMessage', n=target, ex=1): + + sourceLattice = mc.listConnections(source + '.latticeMessage') + if sourceLattice: + sourceLatticeDiv = mc.getAttr(sourceLattice[0] + '.sDivisions') + sourcePoints = [] + if sourceLatticeDiv: + if sourceLattice: + for i in range(sourceLatticeDiv): + point = mc.pointPosition(sourceLattice[0] + '.pt[%s][1][0]' % (i), l=1) + x = mt.lerp(point[0], 1, 0, 0.5, -0.5) + y = mt.lerp(point[1], 1, 0, 1.5, -0.5) + sourcePoints.append((x, y)) + + targetLattice = mc.listConnections(target + '.latticeMessage') + if not targetLattice: + return None + targetLatticeDiv = mc.getAttr(targetLattice[0] + '.sDivisions') + targetPoints = [] + if targetLatticeDiv: + if targetLattice: + for i in range(targetLatticeDiv): + point = mc.pointPosition(targetLattice[0] + '.pt[%s][1][0]' % (i), l=1) + x = mt.lerp(point[0], 1, 0, 0.5, -0.5) + y = mt.lerp(point[1], 1, 0, 1.5, -0.5) + targetPoints.append((x, y)) + + # Add placeholder nodes if needed + if sourceLatticeDiv > targetLatticeDiv: + for i in range(sourceLatticeDiv - targetLatticeDiv): + position = 1 / sourceLatticeDiv * (targetLatticeDiv + i) + targetPoints.append([position, 0.5]) + + blendResult = [[sourcePoints[i][0], mt.lerp(ratio, sourcePoints[i][1], targetPoints[i][1])] + for i in range(min(len(sourcePoints), len(targetPoints)))] + + finalPoints = utils.fromDouble2ToString(blendResult) + return finalPoints + + +def regroupByLayer(groupCustomCollections=None): + # type: (None|set) -> None + """ + Regroup all the curves in all layers and collections in the outliner + Optionally regroups only passed collection IDs + """ + def getCustomName(i, nameDict): + name = 'CT_Layer' + customLayerName = nameDict[i] + if customLayerName: + name = customLayerName + return name + + templateCollections = [] + if groupCustomCollections: + collections = set(groupCustomCollections) + else: + collections = utils.getCollectionsSet() + collections.add('0') + if getOption('groupTemplateCollections'): + collectionsWidget = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + for collection in collections.copy(): + if "template" in collectionsWidget.itemText(int(collection)).lower(): + collections.remove(collection) + templateCollections.append(collection) + + toggleColor.checkColorStorageNode() + counter = 0 + deletedGroupsCounter = 0 + previousGroups = set() + colorDict = dict(eval(mc.getAttr(toggleColor.STORAGE_NODE + '.layerColor'))) + nameDict = dict(eval(mc.getAttr(toggleColor.STORAGE_NODE + '.layerName'))) + hasCollections = len(collections) > 1 + for c in sorted(list(collections)): + for i in range(80): # Iterate over existing groups and layers + name = getCustomName(i, nameDict) + if hasCollections: + name = "%s_%s" % (WIDGETS['layerCollectionsComboBox'].itemText(int(c)).replace(' ', '_'), name) + layerName = '%s_%s' % (name, i) + if groupCustomCollections: + layerName = 'CT_Templates' + grpCurve = 'curveGrp_%s%s_Curve' % (utils.getFormattedCollectionByID(c), i) + if not mc.objExists(layerName): # Create groups if needed + mc.createNode('transform', n=layerName) + else: + if mc.nodeType(layerName) != 'transform': + mc.createNode('transform', n=layerName) + if mc.objExists(grpCurve): + curves = mc.editDisplayLayerMembers(grpCurve, q=1, fn=1) + if not curves: + continue + + if WIDGETS['colorizedRegroup'].isChecked(): + getClr = colorDict[i] + mc.setAttr(layerName + '.useOutlinerColor', 1) + mc.setAttr(layerName + '.outlinerColor', *getClr, typ='float3') + else: + mc.setAttr(layerName + '.useOutlinerColor', 0) + + for curve in curves: + curve = mc.ls(curve, l=1) + if curve: + curve = curve[0] + else: + continue + if mc.attributeQuery('gsmessage', n=curve, ex=1) and mc.connectionInfo(curve + '.gsmessage', id=1): + continue + counter += 1 + grp = selectPart(0, True, curve) + + topGrp = mc.listRelatives(grp, p=1, pa=1) + if topGrp: + previousGroups.add(topGrp[0]) + if topGrp[0] != layerName: + mc.parent(grp, layerName) + else: + mc.parent(grp, layerName) + mc.setAttr(toggleColor.STORAGE_NODE + '.layerColor', str(colorDict), typ='string') + + for ele in previousGroups: # Delete any previous empty groups that exist + children = mc.listRelatives(ele, c=1, pa=1) + if not children: + deletedGroupsCounter += 1 + mc.delete(ele) + for c in collections: + for i in range(80): # Remove Generated empty groups + name = getCustomName(i, nameDict) + if hasCollections: + name = "%s_%s" % (WIDGETS['layerCollectionsComboBox'].itemText(int(c)).replace(' ', '_'), name) + layerName = '%s_%s' % (name, i) + if mc.objExists(layerName) and (mc.nodeType(layerName) == 'transform'): + rel = mc.listRelatives(layerName, c=1, pa=1) + if rel and len(rel) == 0: + mc.delete(layerName) + elif not rel: + mc.delete(layerName) + + reorderingList = [] + for c in list(collections): + for i in range(80): # Reorder groups + name = getCustomName(i, nameDict) + if hasCollections: + name = "%s_%s" % (WIDGETS['layerCollectionsComboBox'].itemText(int(c)).replace(' ', '_'), name) + if mc.objExists('%s_%s' % (name, i)): + reorderingList.append('%s_%s' % (name, i)) + for ele in reorderingList: + mc.reorder(ele, b=1) + + mc.select(cl=1) + + deletedGroupsMsg = '' + if len(previousGroups) > 0: + deletedGroupsMsg = ' and deleted %s empty layer(s).' % deletedGroupsCounter + if templateCollections: + regroupByLayer(templateCollections) + if groupCustomCollections: + MESSAGE.printInView('Regrouped %s templates%s' % (counter, deletedGroupsMsg)) + else: + MESSAGE.printInView('Regrouped %s curve(s)%s' % (counter, deletedGroupsMsg)) + + +def mirrorHair(axis, flip=False): + sel = mc.filterExpand(mc.ls(sl=1), sm=9) + if not sel: + sel = mc.ls(hl=1, o=1) + if not sel: + MESSAGE.warningInView('Select compatible curves.') + return + mirrorRadio = WIDGETS['mirrorRadio'].isChecked() + option = 'Mirroring' if mirrorRadio else 'Flipping' + if flip: + option = 'Flipping' + axisDict = { + 0: [-1, 1, 1], + 1: [1, -1, 1], + 2: [1, 1, -1], + } + if option == 'Mirroring': + newCrv = duplicateCurve(sel) + else: + newCrv = sel + filteredCurves = [] + for crv in newCrv: + # originalProfile = None + originalProfileVector = None + if mc.attributeQuery("Profile", n=crv, ex=1): + originalProfile = mc.getAttr(crv + '.Profile') + mc.setAttr(crv + '.Profile', math.copysign(2, originalProfile)) + firstCv = '%s.cv[0]' % crv + pos = mc.pointPosition(firstCv) + originalProfileVector = utils.getMiddleVertAndNormal(crv, selectPart(2, True, crv)[0], pos) + mc.setAttr(crv + '.Profile', originalProfile) + attrs = utils.resetAndReturnAttrs(crv) + attrs.append(originalProfileVector) + # attrs.append(originalProfile) + filteredCurves.append(attrs) + + if filteredCurves: + mc.scale(axisDict[axis][0], + axisDict[axis][1], + axisDict[axis][2], + [i[0] for i in filteredCurves], + a=1, ws=1, p=[0, 0, 0]) + + # Init progress bar + progress = utils.ProgressBar("Mirroring...", len(filteredCurves)) + + # Fix orientation + for crv in filteredCurves: + if progress.tick(1): + break + curve = crv[0] + firstCv = '%s.cv[0]' % curve + pos = mc.pointPosition(firstCv) + om_pos = om.MPoint(pos) + targetVec = om.MVector( + crv[1][0] * axisDict[axis][0], + crv[1][1] * axisDict[axis][1], + crv[1][2] * axisDict[axis][2] + ) + om_sel = om.MSelectionList() + geo = selectPart(2, True, curve)[0] + om_sel.add(geo) + om_mesh = om.MFnMesh(om_sel.getDagPath(0)) + orientation = curve + '.Orientation' + prevDiff = 360 + currentDiff = 360 + iterator = 0 + maxIterations = 10 + angleTolerance = 0.1 + + guesses = [] + + while (prevDiff >= angleTolerance) and (iterator <= maxIterations): + iterator += 1 + om_cardFaceNormal = om_mesh.getClosestPointAndNormal(om_pos, space=om.MSpace.kWorld)[1] + currentDiff = om_cardFaceNormal.angle(targetVec) * 180.0 / math.pi + currentOrien = mc.getAttr(orientation) + + guesses.append((currentDiff, currentOrien)) + + if currentDiff > prevDiff: + guess = currentOrien - currentDiff + else: + guess = currentOrien + currentDiff + prevDiff = currentDiff + + mc.setAttr(orientation, guess % 360.0) + + # Setting the best guess as the final orientation + finalOrientation = min(guesses, key=lambda t: t[0])[1] + mc.setAttr(orientation, finalOrientation % 360.0) + + # Invert graph + if crv[6]: + invertedTwistGraph = crv[6][0] + widthGraph = crv[6][1] + if invertedTwistGraph[-1] != 'twistCurve': + invertedTwistGraph = crv[6][1] + widthGraph = crv[6][0] + for i in range(len(invertedTwistGraph) - 1): + invertedTwistGraph[i] = (invertedTwistGraph[i][0], 1 - invertedTwistGraph[i][1]) + attributes.setMultiInst(crv[0], invertedTwistGraph) # Inverting Twist Graph + attributes.setMultiInst(crv[0], widthGraph) # Setting Width Graph Back + + # Restore parameters + if crv[2] and crv[2] > 10: + mc.setAttr(curve + '.lengthDivisions', crv[2]) + if crv[3] and crv[3] > 2: + mc.setAttr(curve + '.widthDivisions', crv[3]) + if crv[4] and crv[4] != 0: + mc.setAttr(curve + '.Twist', crv[4] * -1) + if crv[5] and crv[5] != 0: + mc.setAttr(curve + '.invTwist', crv[5] * -1) + + # Fix profile if needed + if crv[7]: + originalProfile = mc.getAttr(curve + '.Profile') + mc.setAttr(curve + '.Profile', math.copysign(2, originalProfile)) + firstCvVec = om.MVector(om_pos) + + expectedVertex = om.MVector( + crv[7][0][0] * axisDict[axis][0], + crv[7][0][1] * axisDict[axis][1], + crv[7][0][2] * axisDict[axis][2] + ) + expectedVector = om.MVector( + crv[7][1][0] * axisDict[axis][0], + crv[7][1][1] * axisDict[axis][1], + crv[7][1][2] * axisDict[axis][2] + ) + profileClosestVertexAndNormal = utils.getMiddleVertAndNormal(curve, selectPart(2, True, curve)[0], firstCvVec) + + # Checking if normals of the flat card are correct. If not, flip the card 180 deg. + normalCheck = expectedVector.angle(profileClosestVertexAndNormal[1]) * 180.0 / math.pi + if abs(normalCheck) >= 90: + mc.setAttr(curve + ".Orientation", (mc.getAttr(curve + ".Orientation") + 180.0) % 360.0) + + # Checking if the angle between two middle vertices is correct. If not, invert the profile. + angleBetween = (firstCvVec - expectedVertex).angle(firstCvVec - om.MVector(profileClosestVertexAndNormal[0])) * 180.0 / math.pi + newProfile = originalProfile * -1 if abs(angleBetween) >= 90 else originalProfile + mc.setAttr(curve + ".Profile", newProfile) + + # Flip UVs + if getOption('flipUVsAfterMirror') and mc.attributeQuery('flipUV', n=curve, ex=1): + mc.setAttr(curve + '.flipUV', not mc.getAttr(curve + '.flipUV')) + + resetCurvePivotPoint() + progress.end() + LOGGER.info("Mirror Finished") + + +def renameSelected(): + sel = sorted(mc.ls(sl=1, tr=1)) + for obj in sel: + grp = selectPart(0, True, obj) + if grp: + try: + newName = WIDGETS['selectedObjectName'].text() + mc.rename(grp, newName + '#') + except BaseException: + MESSAGE.warning('Some items were skipped') + curveControlUI.updateUI() + + +def resetCurvePivotPoint(hk=None, customCurves=None): # Reset Curve Pivot + # type: (None|int, None|list[str]|str) -> None + """Resets pivot point on selected curves or passed as customCurves""" + if customCurves: + sel = customCurves + else: + sel = mc.filterExpand(mc.ls(sl=1, tr=1, l=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one Curve') + return + + # Get modifier + shift = utils.getMod() + mod = True if hk == 2 or (hk is None and shift == 'Shift') else False + + for selElement in sel: + nt = mc.nodeType(mc.listRelatives(selElement, c=1, pa=1)) + if nt != 'nurbsCurve' and nt != 'bezierCurve': + continue + mc.select(selElement) + transforms = mc.xform(selElement, q=1, t=1) + curve = mc.listRelatives(selElement, c=1, pa=1) + cvNum = '0' + if mod: + cvNum = str(mc.getAttr(selElement + '.controlPoints', s=1) - 1) + cv = mc.pointPosition((curve[0] + ".cv[" + cvNum + "]"), w=1) + mc.xform(ws=1, piv=(transforms[0], transforms[1], transforms[2])) + mc.xform(ws=1, piv=(cv[0], cv[1], cv[2])) + mc.manipMoveContext('Move', e=1, mode=0) + mc.manipRotateContext('Rotate', e=1, mode=0) + mc.manipScaleContext('Scale', e=1, mode=0) + mc.select(sel) + + +def selectGeoCurveUV(): + curve = mc.ls(sl=1, tr=1) + geo = selectPart(2, True, curve[0])[0] + mc.select(geo, r=1) + mc.select(curve, add=1) + + +def toggleGeoEdit(): # Toggle Geometry Edit Hotkey + value = None + for i in range(80): + geo = 'curveGrp_%s_Geo' % i + if (utils.attrExists(geo, 'displayType')): + if value is None: + value = mc.getAttr(geo + '.displayType') + if value == 2: + mc.setAttr(geo + '.displayType', 0) + else: + mc.setAttr(geo + '.displayType', 2) + updateMainUI() + + +def extractCurveGeo(layerNum, checkButtons=True): + # type: (str|int, bool) -> None + """Extract Geo from single Layer via marking menu""" + + create.initialize() + + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + collection = utils.getFormattedCollectionByID(collectionID) + + geo = "curveGrp_%s%s_Geo" % (collection, layerNum) + curve = "curveGrp_%s%s_Curve" % (collection, layerNum) + + if toggleColor.colorEnabled(): + toggleColor.disableColors() + + result = extractGeo(geo, curve, layerNum) + if not result: + MESSAGE.warning("Layer is Empty") + + if checkButtons: + updateMainUI() + + +def extractGeo(geoLayerName, curveLayerName, layerNum, hideLayer=True): + # type: (str, str, int, bool) -> list[str] + """Extracts geo from layer names, optionally hides the original layers""" + outlinerSync = getOption('syncOutlinerLayerVis') + if mc.objExists(geoLayerName): + groups = [] + if outlinerSync: + layerCurves = mc.editDisplayLayerMembers(curveLayerName, q=1, fn=1) + if not layerCurves: + return + for i in layerCurves: + parent = mc.listRelatives(i, p=1, pa=1) + if parent: + groups += parent + if hideLayer: + if outlinerSync: + for grp in groups: + mc.setAttr(grp + '.v', 0) + mc.setAttr(geoLayerName + '.visibility', 0) + mc.setAttr(curveLayerName + '.visibility', 0) + if mc.layerButton(geoLayerName, q=1, ex=1): + mc.layerButton(geoLayerName, e=1, lv=0) + if mc.layerButton(geoLayerName, q=1, ex=1): + mc.layerButton(geoLayerName, e=1, lv=0) + sel = mc.editDisplayLayerMembers(geoLayerName, q=1, fn=1) + sel = mc.filterExpand(sel, sm=12) + if sel: + dupList = list() + for ele in sel: + dup = mc.duplicate(ele, rc=1) + dupList.append(dup[0]) + mc.setAttr(dup[0] + '.inheritsTransform', 1) + mc.editDisplayLayerMembers('defaultLayer', dupList, nr=1) + split = curveLayerName.split("_") + collectionID = int(split[1]) if len(split) == 4 else 0 + collectionName = WIDGETS['layerCollectionsComboBox'].itemText(collectionID) + extracted = 'extractedGeo_%s_%s' % (collectionName, layerNum) + if mc.objExists(extracted): + finalGeo = mc.parent(dupList, extracted) + return [extracted + "|" + x for x in finalGeo] + else: + finalGeo = mc.group(dupList, w=1, n=extracted) + return [finalGeo + "|" + x for x in dupList] + return [] + + +def extractSelectedCurves(mod=None, hotkey=False): + # type: (str, bool) -> None + """Extract geo from selected curves""" + + sel = mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one Curve') + return 0 + + if not hotkey: + mod = utils.getMod() + + isColorEnabled = toggleColor.colorEnabled() + if isColorEnabled: + toggleColor.disableColors() + + geometry = [] + for part in sel: + geo = selectPart(2, True, part) + if geo: + geo = geo[0] + else: + MESSAGE.warningInView('Curve "%s" failed to extract. Check the outliner for any loose curves.' % part) + continue + geometry.append(geo) + geometry = mc.filterExpand(geometry, sm=12) + if not geometry: + return + allGeo = [] + progress = utils.ProgressBar('Extracting Geometry', len(geometry)) + try: + for geo in geometry: + progress.tick(1) + dup = mc.duplicate(geo, rc=1) + mc.setAttr(dup[0] + '.inheritsTransform', 1) + mc.editDisplayLayerMembers('defaultLayer', dup[0], nr=1) + allGeo += dup + except Exception as e: + LOGGER.exception(e) + finally: + progress.end() + + parentGrp = mc.group(allGeo, w=1, n='extractedGeo_#') + allGeo = mc.listRelatives(parentGrp, c=1, pa=1) + + if not mod or "Shift" not in mod: + if len(allGeo) > 1: + combinedGeo = mc.polyUnite(allGeo, cp=1) + mc.delete(combinedGeo, ch=1) + else: + combinedGeo = mc.parent(allGeo, w=1) + mc.delete(parentGrp) + combinedGeo = mc.filterExpand([x for x in combinedGeo if mc.objExists(x)], sm=12) + allGeo = mc.rename(combinedGeo[-1], 'extractedGeo_#') + + mc.select(allGeo, r=1) + + # Open export dialog + if mod and "Ctrl" in mod: + mel.eval("ExportSelection") + mc.delete(allGeo) + try: + mc.delete(parentGrp) + except BaseException: + pass + + if isColorEnabled: + toggleColor.enableColors() + + updateMainUI() + + +def extractAllCurves(mod=None, hotkey=False): + # type: (str|None, bool) -> None + """ + Extracts geo from all layers + Modifiers: + Default -> extract geo and combine + Shift -> extract geo + Ctrl -> extract geo, combine and open export menu, delete extracted + Shift+Ctrl -> extract geo and open export menu, delete extracted + """ + if not hotkey: + mod = utils.getMod() + + isColorEnabled = toggleColor.colorEnabled() + if isColorEnabled: + toggleColor.disableColors() + + numOfLayers = WIDGETS['curveGrp0'].LAYERS - 1 + if not WIDGETS['ignoreLastLayer'].isChecked(): + numOfLayers = WIDGETS['curveGrp0'].LAYERS + + allCollections = utils.getCollectionsSet() + allCollections.add('0') + if getOption('ignoreTemplateCollections'): + collectionsWidget = WIDGETS['layerCollectionsComboBox'] # type: wrap.LayerCollectionWidget + for collection in allCollections.copy(): + if "template" in collectionsWidget.itemText(int(collection)).lower(): + allCollections.remove(collection) + allGeo = [] + shouldHideLayers = (mod and "Ctrl" not in mod) or not mod + progress = utils.ProgressBar('Extracting Geometry', numOfLayers * len(allCollections)) + try: + for collection in allCollections: + formattedCollection = utils.getFormattedCollectionByID(collection) + for i in range(numOfLayers): + if progress.tick(1): + break + geo = "curveGrp_%s%s_Geo" % (formattedCollection, i) + curve = "curveGrp_%s%s_Curve" % (formattedCollection, i) + if mc.objExists(geo) and mc.objExists(curve): + allGeo += extractGeo(geo, curve, i, shouldHideLayers) + except Exception as e: + LOGGER.debug(e) + finally: + progress.end() + + # Delete parent groups + parentGrps = set(mc.listRelatives(allGeo, p=1)) + if not mod or "Shift" not in mod: + if len(allGeo) > 1: + combinedGeo = mc.polyUnite(allGeo, cp=1) + mc.delete(combinedGeo, ch=1) + combinedGeo = mc.filterExpand([x for x in combinedGeo if mc.objExists(x)], sm=12) + else: + combinedGeo = mc.parent(allGeo, w=1) + parentGrps = [x for x in parentGrps if mc.objExists(x)] + if parentGrps: + mc.delete(parentGrps) + allGeo = mc.rename(combinedGeo[-1], 'extractedGeo_#') + + mc.select(allGeo, r=1) + + # Open export dialog + if mod and "Ctrl" in mod: + mel.eval("ExportSelection") + mc.delete(allGeo) + parentGrps = [x for x in parentGrps if mc.objExists(x)] + if parentGrps: + mc.delete(parentGrps) + if isColorEnabled: + toggleColor.enableColors() + + updateMainUI() + + +def toggleLayerVisibility(layerNum): # Toggle Layer Visibility + outlinerSync = getOption('syncOutlinerLayerVis') + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + collection = utils.getFormattedCollectionByID(collectionID) + curve = "curveGrp_%s%s_Curve" % (collection, layerNum) + geo = "curveGrp_%s%s_Geo" % (collection, layerNum) + inst = "curveGrp_%s%s_Inst" % (collection, layerNum) + if (utils.attrExists(curve, 'visibility')): + groups = [] + if outlinerSync: + layerCurves = mc.editDisplayLayerMembers(curve, q=1, fn=1) + if not layerCurves: + MESSAGE.warningInView('Layer is Empty') + return + for i in layerCurves: + parent = mc.listRelatives(i, p=1, pa=1) + if parent: + groups += parent + current = mc.getAttr(curve + '.visibility') + if current == 1: + mc.setAttr(curve + '.visibility', 0) + if mc.layerButton(curve, q=1, ex=1): + mc.layerButton(curve, e=1, lv=0) + mc.setAttr(geo + '.visibility', 0) + if mc.layerButton(geo, q=1, ex=1): + mc.layerButton(geo, e=1, lv=0) + mc.setAttr(inst + '.visibility', 0) + if mc.layerButton(inst, q=1, ex=1): + mc.layerButton(inst, e=1, lv=0) + for grp in groups: + mc.setAttr(grp + '.v', 0) + else: + mc.setAttr(curve + '.visibility', 1) + if mc.layerButton(curve, q=1, ex=1): + mc.layerButton(curve, e=1, lv=1) + mc.setAttr(geo + '.visibility', 1) + if mc.layerButton(geo, q=1, ex=1): + mc.layerButton(geo, e=1, lv=1) + mc.setAttr(inst + '.visibility', 0) + if mc.layerButton(inst, q=1, ex=1): + mc.layerButton(inst, e=1, lv=0) + for grp in groups: + mc.setAttr(grp + '.v', 1) + else: + MESSAGE.warningInView('Layer is Empty') + + +def toggleObjVisibility(layerNum, obj): + # type: (str|int, int) -> None + """Toggle object type visibility in layer""" + name = str() + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + collection = utils.getFormattedCollectionByID(collectionID) + if obj == 0: + name = "curveGrp_%s%s_Curve" % (collection, layerNum) + elif obj == 1: + name = "curveGrp_%s%s_Geo" % (collection, layerNum) + if utils.attrExists(name, 'visibility'): + current = mc.getAttr(name + '.visibility') + if current == 1: + mc.setAttr(name + '.visibility', 0) + if mc.layerButton(name, q=1, ex=1): + mc.layerButton(name, e=1, lv=0) + else: + mc.setAttr(name + '.visibility', 1) + if mc.layerButton(name, q=1, ex=1): + mc.layerButton(name, e=1, lv=1) + else: + MESSAGE.warningInView('Layer is Empty') + + +def curveLayerSelectObj(layerNum, obj, *_): # Select Object from Layer + mod = utils.getMod() + if obj == -1: + if mod != 'Shift' and mod != 'Ctrl': + return 0 + else: + obj = 0 + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + collection = utils.getFormattedCollectionByID(collectionID) + geo = 'curveGrp_%s%s_Geo' % (collection, layerNum) + curve = 'curveGrp_%s%s_Curve' % (collection, layerNum) + if mc.objExists(geo): + sel = mc.ls(sl=1) + if (obj == 1): + mc.select(mc.editDisplayLayerMembers(geo, q=1, fn=1)) + if (obj == 0): + selection = [] + unfilteredCurves = mc.editDisplayLayerMembers(curve, q=1, fn=1) + if not unfilteredCurves: + MESSAGE.warningInView('Layer is Empty') + return + for ele in unfilteredCurves: + if mc.attributeQuery('gsmessage', n=ele, ex=1) and mc.connectionInfo(ele + '.gsmessage', id=1): + continue + selection.append(ele) + mc.select(selection) + if (not WIDGETS['replacingCurveLayerSelection'].isChecked() or (mod == 'Shift')): + mc.select(sel, add=1) + else: + MESSAGE.warningInView('Layer is Empty') + + +def curveGeometryEditToggle(layerNum): + # type: (str|int) -> None + """Toggle Geo Editing on Layer""" + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + collection = utils.getFormattedCollectionByID(collectionID) + geo = 'curveGrp_%s%s_Geo' % (collection, layerNum) + if (utils.attrExists(geo, 'displayType')): + currentValue = mc.getAttr(geo + '.displayType') + if currentValue == 2: + mc.setAttr(geo + '.displayType', 0) + curveLayerSelectObj(layerNum, 1) + else: + mc.setAttr(geo + '.displayType', 2) + else: + MESSAGE.warningInView('Layer is Empty') + + +def curveAddToLayer(targetLayer, sourceLayer=None, inputCurves=None, targetCollection=None): + # type: (str|int, str|int, list[str], int) -> None + """Add Selected Curves to Layer""" + create.initialize() + collectionID = WIDGETS['layerCollectionsComboBox'].currentIndex() + if targetCollection is not None: + collectionID = int(targetCollection) + curve, geo, inst = utils.getFormattedLayerNames(collectionID, targetLayer) + sel = mc.ls(sl=1, tr=1) + if inputCurves: + sel = inputCurves + if sourceLayer: + collection = utils.getFormattedCollectionByID(collectionID) + sourceLayer = "curveGrp_%s%s_Curve" % (collection, sourceLayer) + if mc.objExists(sourceLayer): + sel = mc.editDisplayLayerMembers(sourceLayer, q=1, fn=1) + else: + return 0 + origSel = sel + if sel: + for ele in sel: + if ('pathCurve' in ele) or ('geoCard' in ele) or ('geoTube' in ele): + eleGrp = mc.listRelatives(ele, p=1, pa=1) + if not eleGrp: + MESSAGE.warningInView('Wrong Selection: %s is skipped' % ele) + continue + components = separateComponents(eleGrp[0]) + if not components: + continue + if not mc.objExists(curve): + mc.select(cl=1) + utils.createNewDisplayLayer(curve) + if not mc.objExists(geo): + utils.createNewDisplayLayer(geo) + mc.setAttr(geo + '.enabled', 1) + mc.setAttr(geo + '.displayType', 2) + if not mc.objExists(inst): + utils.createNewDisplayLayer(inst) + mc.setAttr(inst + '.enabled', 1) + mc.setAttr(inst + '.visibility', 0) + mc.setAttr(inst + '.displayType', 2) + mc.editDisplayLayerMembers(curve, components[0], nr=1) + mc.editDisplayLayerMembers(inst, components[1], nr=1) + mc.editDisplayLayerMembers(geo, components[2], nr=1) + if WIDGETS['boundCurvesFollowParent'].isChecked(): + ele = getAllConnectedCurves(ele) + for i in ele: + if mc.attributeQuery('gsmessage', n=i, ex=1): + if mc.connectionInfo(i + '.gsmessage', isSource=1): + childCards = mc.listConnections(i + '.gsmessage', d=1, s=0, t='transform') + if childCards: + for card in childCards: + components = separateComponents(selectPart(0, True, card)) + mc.editDisplayLayerMembers(curve, components[0], nr=1) + mc.editDisplayLayerMembers(inst, components[1], nr=1) + mc.editDisplayLayerMembers(geo, components[2], nr=1) + else: + MESSAGE.warningInView('Wrong Selection: %s is skipped' % ele) + deleteUnusedLayers() + layerCollections.updateDefaultLayerNode() + layerCollections.updateCollectionNames() + if WIDGETS['syncCurveColor'].isChecked(): + toggleColor.syncCurveColors() + if WIDGETS['colorMode'].isChecked(): + toggleColor.onLayerChange(sel, geo) + mc.select(origSel, r=1) + else: + MESSAGE.warningInView('Nothing is Selected') + + +def separateComponents(inputGroup): + tr = mc.listRelatives(inputGroup, pa=1) + components = [None, None, None] + for comp in tr: + if 'pathCurve' in comp: + components[0] = comp + elif 'instances' in comp: + components[1] = comp + elif ('geoCard' in comp) or ('geoTube' in comp): + components[2] = comp + if len(components) != 3: + return + else: + return components + + +def getAllConnectedCurves(inputCurves): + returnCurves = [] + if isinstance(inputCurves, list): + currentCurves = inputCurves + else: + currentCurves = [inputCurves] + + returnCurves = currentCurves + + iterationCurves = currentCurves + safety = 0 + while iterationCurves: + safety = safety + 1 + if safety > 50: + break + boundCurves = [] + for curve in iterationCurves: + condition = (mc.attributeQuery('gsmessage', n=curve, ex=1) and + mc.connectionInfo(curve + '.gsmessage', isSource=1)) + if condition: + newCurves = mc.listConnections(curve + '.gsmessage', d=1, s=0, t='transform') + if newCurves: + boundCurves += newCurves + else: + continue + if boundCurves: + iterationCurves = False + iterationCurves = boundCurves + returnCurves += iterationCurves + else: + iterationCurves = False + break + + return returnCurves + + +def getCurveLayer(curve): + if not curve: + return + layer = mc.listConnections(curve, type='displayLayer') + layerID = re.findall(r'\d+', layer[0])[0] + return layerID + + +def filterBoundCurves(curves): + if isinstance(curves, list): + curves = curves + else: + curves = [curves] + + filteredCurves = [] + + for curve in curves: + if (mc.attributeQuery('gsmessage', n=curve, ex=1) and + mc.connectionInfo(curve + '.gsmessage', id=1)): + continue + filteredCurves.append(curve) + + return filteredCurves + + +def duplicateCurve(customSel=None): + # type: (list[str]|None) -> None + """Duplicate selected Curves or curves list passed as an argument""" + create.initialize() + sel = mc.ls(sl=1, fl=1) + if customSel: + sel = customSel + if not sel: + MESSAGE.warningInView('Select at least one curve') + return + fe = mc.filterExpand(sel, sm=(30, 28)) + if fe: + obj = mc.ls(sel, o=1) + sel = [] + for ele in obj: + rel = mc.listRelatives(ele, p=1, pa=1) + sel.append(rel[0]) + sel = list(set(sel)) + finalSel = list() + copyThickness = list() + mc.select(cl=1) + if not customSel: + progressBar = utils.ProgressBar('Duplicating Curves', len(sel)) + try: + sel = filterBoundCurves(sel) + try: + utils.deleteKeys(sel) + except BaseException: + pass + sel = list(set(selectPart(0, True, sel))) # Filter out duplicates + for ele in sel: + if not customSel: + progressBar.tick(1) + if ('pathCurve_inst' in ele) or ('profileCurve_inst' in ele): + ele = mc.listRelatives(ele, p=1, pa=1)[0] + if any([x in ele for x in ['geoCard', 'pathCurve', 'geoTube', 'instances', 'origCurves']]): + parent = mc.listRelatives(ele, p=1, pa=1) + origCrv = None + for curve in mc.listRelatives(parent, c=1, pa=1): + if 'pathCurve' in curve: + origCrv = curve + break + dup = mc.duplicate(parent, rr=1, rc=1, un=1) + dupCrv = None + for curve in mc.listRelatives(dup, c=1, pa=1): + if 'pathCurve' in curve: + dupCrv = curve + break + if origCrv and dupCrv: + copyThickness.append((origCrv, dupCrv)) + elif dupCrv: + copyThickness.append(dupCrv) + finalSel.append(dupCrv) + else: + rel = mc.listRelatives(ele, c=1, pa=1) + if not rel: + continue + dup = mc.duplicate(ele, rr=1, rc=1, un=1) + for curve in mc.listRelatives(dup, c=1, pa=1): + if 'pathCurve' in curve: + copyThickness.append(curve) + finalSel.append(curve) + break + except BaseException: + pass + finally: + if not customSel: + progressBar.end() + if copyThickness: + setCurveThickness(copyThickness) + mc.select(finalSel, r=1) + return (finalSel) + + +def deleteSelectedCurves(): + """Deletes selected curves in a safe way. Also deletes other objects""" + sel = mc.ls(sl=1, tr=1) + sel = selectPart(0, True, sel) + if not sel: + return + mc.delete(sel) + print(" ") # Just to avoid annoying errors during bind curve deletion + + +def smoothCurve(inputCurves=None): + """Smoothes selected curve""" + sel = mc.ls(sl=1) + if not sel: + MESSAGE.warningInView('Select at least one curve') + return 0 + if inputCurves: + sel = inputCurves + selCrv = mc.filterExpand(sel, sm=9) + selCV = mc.filterExpand(sel, sm=28) + if not selCrv and not selCV: + MESSAGE.warningInView('Select at least 1 curve to smooth') + return 0 + if selCV and len(selCV) < 3: + MESSAGE.warningInView('Select at least 3 CVs to smooth') + return 0 + mult = 1 + if mc.menuItem('gsSmoothMult3', q=1, rb=1): + mult = 3 + if mc.menuItem('gsSmoothMult5', q=1, rb=1): + mult = 5 + if mc.menuItem('gsSmoothMult10', q=1, rb=1): + mult = 10 + if inputCurves: + mult = 10 + smoothing = math.ceil((100 - mc.floatSliderGrp('gsFactorSlider', q=1, v=1)) / 20) + if selCrv: # If curve is selected + sel = mc.ls(selCrv, dag=1, s=1) + for obj in sel: + CVcount = mc.getAttr(obj + '.cp', s=1) + if CVcount >= 3: + for _ in range(0, mult): + cPoint = mc.getAttr(obj + '.cp[:]') + finalPoints = list() + finalPoints.append(tuple(cPoint[0])) + firstP = om.MFloatVector(cPoint[0][0], cPoint[0][1], cPoint[0][2]) + activeP = om.MFloatVector(cPoint[1][0], cPoint[1][1], cPoint[1][2]) + for i in range(2, CVcount): + nextP = om.MFloatVector(cPoint[i][0], cPoint[i][1], cPoint[i][2]) + activePpos = (activeP + firstP + nextP) / 3 + for _ in range(1, int(smoothing)): + activePpos = (activeP + activePpos) / 2 + finalPoints.append(tuple([activePpos.x, activePpos.y, activePpos.z])) + firstP = activeP + activeP = nextP + finalPoints.append(tuple(cPoint[-1])) + cmd = ('mc.setAttr("' + str(obj) + '.cp[:]"') + for i in range(len(finalPoints)): + for z in range(3): + cmd += (',' + str(finalPoints[i][z])) + cmd += ')' + eval(cmd) + else: # If CVs are selected + curveDict = dict() + for cv in selCV: + obj = mc.ls(cv, o=1)[0] + CVnum = re.findall(r'\d+', re.findall(r'\[\d+\]', cv)[0])[0] + if obj in curveDict: + curveDict[obj].append(int(CVnum)) + else: + curveDict[obj] = [int(CVnum)] + for obj in curveDict: + CVs = curveDict[obj] + if len(CVs) < 3: + continue + CVs.sort() + CVsFinal = [CVs[0]] + for i in range(len(CVs) - 1): + if (int(CVs[i + 1]) - int(CVs[i])) >= 2: + break + CVsFinal.append(CVs[i + 1]) + CVcount = len(CVs) + if CVcount >= 3: + for _ in range(0, mult): + cPoint = list() + for cv in CVsFinal: + cPoint.append(mc.getAttr(obj + '.cp[%s]' % cv)[0]) + finalPoints = list() + finalPoints.append(tuple(cPoint[0])) + firstP = om.MFloatVector(cPoint[0][0], cPoint[0][1], cPoint[0][2]) + activeP = om.MFloatVector(cPoint[1][0], cPoint[1][1], cPoint[1][2]) + for i in range(2, CVcount): + nextP = om.MFloatVector(cPoint[i][0], cPoint[i][1], cPoint[i][2]) + activePpos = (activeP + firstP + nextP) / 3 + for _ in range(1, int(smoothing)): + activePpos = (activeP + activePpos) / 2 + finalPoints.append(tuple([activePpos.x, activePpos.y, activePpos.z])) + firstP = activeP + activeP = nextP + finalPoints.append(tuple(cPoint[-1])) + cmd = ('mc.setAttr("' + str(obj) + '.cp[%s:%s]"' % (CVsFinal[0], CVsFinal[-1])) + for i in range(len(finalPoints)): + for z in range(3): + cmd += (',' + str(finalPoints[i][z])) + cmd += ')' + eval(cmd) + + +def extendCurve(): # Extends curve length + sel = mc.filterExpand(mc.ls(sl=1, fl=1, o=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one curve') + return 0 + factor = mc.floatSliderGrp('gsFactorSlider', q=1, v=1) + if factor < 10: + factor = 10 + for obj in sel: + distance = (mc.arclen(obj) * (float(factor) ** .5) / 100) / 10 + getCV = mc.getAttr(obj + '.spans') + deg = mc.getAttr(obj + '.degree') + if ((float(factor) ** .5) / 100 > .25): + getCV += math.ceil(float(factor) / 10) + mc.extendCurve(obj, et=2, d=(distance * factor), cos=0, em=0, s=0, jn=1, rmk=1, rpo=1) + mc.rebuildCurve(obj, d=deg, kt=1, rt=0, s=getCV) + mc.select(sel, r=1) + + +def reduceCurve(): # Reduces curve length + sel = mc.ls(sl=1, fl=1, o=1) + if not sel: + MESSAGE.warningInView('Select at least one curve') + return 0 + factor = (mc.floatSliderGrp('gsFactorSlider', q=1, v=1) / 100) ** 1.5 + for obj in sel: + degree = mc.getAttr(obj + '.degree') + spans = mc.getAttr(obj + '.spans') + minimum = mc.getAttr(obj + '.min') + maximum = mc.getAttr(obj + '.max') + posRange = maximum - (maximum - minimum) * factor + invRange = minimum + (maximum - minimum) * factor + iteration = int(math.floor(invRange)) + if invRange < .05: + posRange = maximum - (maximum - minimum) * 0.05 + mc.insertKnotCurve(obj + '.u[' + str(posRange) + ']', cos=1, nk=1, ib=0, rpo=1) + for _ in range(iteration + 1): + mc.delete(obj + '.ep[' + str(mc.getAttr(obj + '.spans')) + ']') + try: + mc.rebuildCurve(obj, s=spans, d=degree, tol=0.0001, rpo=1, rt=0, end=1, kr=1, kcp=0, kep=1, kt=0) + except BaseException: + pass + mc.select(sel, r=1) + + +def selectPart(selType, justReturn=False, inputObj=None): # Selects Curve, Geometry or Group + # type: (int, bool, list|str) -> (None|list) + """ selType: 0 - Group, 1 - Curve, 2 - Geo , 3 - Bound Curves""" + sel = mc.ls(sl=1, o=1, fl=1) + if not sel: + sel = mc.ls(hl=1, o=1, fl=1) + if inputObj: + if isinstance(inputObj, list): + sel = inputObj + else: + sel = [inputObj] + finSel = list() + for part in sel: + if mc.nodeType(part) != 'transform': + try: + part = mc.listRelatives(part, p=1, pa=1)[0] + except BaseException: + return + ch = list() + grp = str() + if ('pathCurve' in part) or ('instance' in part) or ('geoCard' in part) or ('geoTube' in part): + grp = mc.listRelatives(part, p=1, pa=1) + if grp: + grp = grp[0] + else: + return 0 + ch = mc.listRelatives(grp, c=1, pa=1) + else: + grp = part + ch = mc.listRelatives(part, c=1, pa=1) + if selType == 0: + finSel.append(grp) + else: + if not ch: + return + for ele in ch: + if selType == 1: + if 'pathCurve' in ele: + finSel.append(ele) + break + elif selType == 2: + if ('geoCard' in ele) or ('geoTube' in ele): + finSel.append(ele) + break + elif selType == 3: + if ('origCurves' in ele): + finSel.append(ele) + break + if justReturn: + return finSel + mc.select(finSel, r=1) + + +def groupCurves(): # Groups selected Curves in the Outliner + selectPart(0) + name = WIDGETS['gsGroupNameTextField'].text() + if not name: + name = 'crvGrp#' + sel = mc.ls(sl=1, fl=1) + if len(sel) > 0: + upGrp = mc.listRelatives(sel[0], p=1, pa=1) + grp = str() + if upGrp is None: + grp = mc.group(sel, n=name) + else: + grp = mc.group(sel, n=name, p=upGrp[0]) + mc.connectAttr(grp + '.sx', grp + '.sy', f=1) + mc.connectAttr(grp + '.sx', grp + '.sz', f=1) + else: + MESSAGE.warningInView('Select at least one Curve') + + +def controlCurveCreate(): + """Creates Control Curve from selected Curves""" + sel = mc.ls(sl=1, tr=1, fl=1) + if not sel: + sel = mc.listRelatives(mc.ls(sl=1, o=1), p=1, pa=1) + sel = mc.filterExpand(sel, sm=9) + if not sel: + MESSAGE.warningInView('No nurbs curves selected') + return + + # Finding closest points to ref CV + refCurve = sel[0] + refCV = "{}.ep[{}]".format(refCurve, 0) + closestEPs = [] + for curve in sel: + om_selList = om.MSelectionList() + om_selList.add(curve) + om_curve = om.MFnNurbsCurve(om_selList.getDagPath(0)) + om_closestPoint = om_curve.closestPoint(om.MPoint(mc.pointPosition(refCV)), space=om.MSpace.kWorld) + spans = mc.getAttr(curve + '.spans') + minmax = mc.getAttr(curve + '.minMaxValue')[0] + uMid = minmax[1] / 2.0 + finalEP = spans if om_closestPoint[1] > uMid else 0 + closestEPs.append((curve, finalEP)) + + # Finding average points between the curves + spans = mc.getAttr(sel[0] + '.spans') + step = 1.0 / float(spans + 4) + pp = [] + u = 0.0 + for _ in range(spans + 5): + point = om.MFloatVector() + for curve, ep in closestEPs: + minmax = mc.getAttr(curve + '.minMaxValue')[0] + if ep == 0: + uNorm = minmax[0] + (minmax[1] - minmax[0]) * u + else: + uNorm = minmax[1] - (minmax[1] - minmax[0]) * u + pp0 = mc.pointPosition(curve + '.u[%s]' % uNorm) + point += om.MFloatVector(pp0[0], pp0[1], pp0[2]) + u += step + pp.append(point / len(closestEPs)) + + # Constructing the curve + controlCurve = mc.curve(ep=pp, d=3) + controlCurve = mc.rebuildCurve(controlCurve, kr=2, rt=0, s=spans)[0] + controlCurve = mc.rename(controlCurve, 'controlCurve#') + mc.addAttr(controlCurve, ln='isControlCurve', dt='string') + + # Selection list for Warp deformer + selList = [] + for curve in sel: + selList.append(curve) + selList.append(controlCurve) + mc.select(selList, r=1) + + # Creating Warp deformer + mel.eval('CreateWrap;') + mc.setAttr(controlCurve + 'Base.hiddenInOutliner', 1) + mc.setAttr(controlCurve + '.wrapSamples', 100) + mc.setAttr(controlCurve + '.dropoff', 20) + mc.setAttr(mc.ls(controlCurve, dag=1, s=1)[0] + '.lineWidth', 3) + mc.setAttr(controlCurve + '.overrideEnabled', 1) + mc.setAttr(controlCurve + '.overrideRGBColors', 1) + mc.setAttr(controlCurve + '.overrideColorRGB', 0, 1, 0) + mc.select(controlCurve, r=1) + resetCurvePivotPoint() + LOGGER.info('Control Curve Created') + # mc.evalDeferred(lambda: print(), lp=1) + + +def controlCurveApply(): # Deletes selected Control Curve and clears History + sel = mc.ls(sl=1, fl=1, o=1, dag=1, s=1) + if not len(sel): + MESSAGE.warningInView('Select Control Curve to Apply') + return 0 + wraps = mc.listConnections(sel[0], t='wrap', et=1, d=1, s=0) + if not wraps: + MESSAGE.warningInView('Select Control Curve to Apply') + return 0 + back = mc.listConnections(wraps[0], s=1, d=1, et=1, t='nurbsCurve') + forth = mc.listConnections(back, s=0, d=1, et=1, t='wrap') + cleanWraps = list(dict.fromkeys(forth)) + curves = mc.listConnections(cleanWraps, s=1, d=0, et=1, t='nurbsCurve') + cleanCurves = list(dict.fromkeys(curves)) + other = [] + control = [] + for curve in cleanCurves: + if utils.attrExists(curve, 'isControlCurve'): + if utils.attrExists(curve, 'wrapSamples') and mc.listConnections(curve + '.wrapSamples'): + control.append(curve) + else: + other.append(curve) + else: + other.append(curve) + mc.delete(other, ch=1) + mc.delete(control) + mc.select(other, r=1) + + +def edgeToCurve(enableProgressBar=True): # Converts selected edge groups to curves + sel = mc.ls(sl=1, fl=1) + sel = mc.filterExpand(sel, sm=32) + if not sel: + MESSAGE.warningInView('Select Edges to Convert') + return 0 + result = str() + + if len(sel) > 1000: + message = 'Warning!\n\nMore than 1000 edges selected.\nComputation can take a long time ( > 10 seconds)\nContinue?\n\nNote: Selecting smaller edge groups is much faster\n' + result = mc.confirmDialog(ma='Center', icn='question', t='More than 1000 edges selected', m=message, + button=['OK', 'Cancel'], cancelButton='Cancel', ds='Cancel') + else: + result = 'OK' + if result != 'OK': + return 0 + + # Progress bar init + if enableProgressBar: + progressBar = utils.ProgressBar('Converting Edges', len(sel)) + + # Sort Edges to Edge Groups + obj = mc.ls(sl=1, o=1) + edges = dict() + for i in range(len(sel)): + polyInfo = mc.polyInfo(sel[i], ev=1)[0].format() + polyInfo = polyInfo.split() + edges[i] = (int(polyInfo[1][0:-1]), int(polyInfo[2]), int(polyInfo[3])) + + finalCollection = list() + while (len(edges) > 0): + edgeCollection = list() + edge = edges.popitem()[1] + origComp = edge[0] + # fe0 = edge[1] + # fe1 = fe0 + fv0 = (edge[1], edge[2]) + fv1 = fv0 + iteration = len(edges) + for i in range(iteration): + for key in edges: + nv = (edges[key][1], edges[key][2]) + if fv0[0] == nv[0] or fv0[0] == nv[1] or fv0[1] == nv[0] or fv0[1] == nv[1]: + edgeCollection.append(edges[key][0]) + # fe0 = edges[key][0] + fv0 = (nv[0], nv[1]) + edges.pop(key) + break + if fv1[0] == nv[0] or fv1[0] == nv[1] or fv1[1] == nv[0] or fv1[1] == nv[1]: + edgeCollection.append(edges[key][0]) + # fe1 = edges[key][0] + fv1 = (nv[0], nv[1]) + edges.pop(key) + break + edgeCollection.append(origComp) + finalCollection.append(edgeCollection) + + if enableProgressBar and progressBar.tick(len(edgeCollection)): + return + + if enableProgressBar: + progressBar.end() + + mode = 3 + if utils.getMod() == "Shift": + mode = 1 + + grp = list() + mc.select(cl=1) + + # Create cures from edge groups + for group in finalCollection: + edgeSelection = list() + for edge in group: + edgeSelection.append(obj[0] + '.e[' + str(edge) + ']') + mc.select(edgeSelection, r=1) + tmp = mel.eval('string $gsTempPolyToCurve[] = `polyToCurve -form 2 -degree ' + str( + mode) + ' -conformToSmoothMeshPreview 1`') + grp.append(tmp[0]) + mc.select(grp, r=1) + + +def orientToFaceNormals(): + mesh = WIDGETS['gsOrientMeshName'].text() + if not mesh: + MESSAGE.warningInView('Add the geo to the Target Field') + return + if not mc.objExists(mesh): + MESSAGE.warningInView('Target Mesh Not Found') + return + sel = mc.filterExpand(mc.ls(sl=1, fl=1, o=1), sm=9) + if not sel: + MESSAGE.warningInView('Select at least one curve') + return + N = WIDGETS['gsIterationsSlider'].getValue() # Number of iterations per curve + angleTolerance = WIDGETS['gsMinimumAngle'].getValue() + isRefresh = WIDGETS['orientRefreshViewport'].isChecked() + progress = None + if len(sel) > 10: + progress = utils.ProgressBar('Orienting Selection', len(sel)) + try: + for curve in sel: + if progress and progress.tick(1): + return + lDiv = mc.getAttr(curve + '.lengthDivisions') if mc.attributeQuery('lengthDivisions', n=curve, ex=1) else None + wDiv = mc.getAttr(curve + '.widthDivisions') if mc.attributeQuery('widthDivisions', n=curve, ex=1) else None + twist = mc.getAttr(curve + '.Twist') if mc.attributeQuery('Twist', n=curve, ex=1) else None + # invTwist = mc.getAttr(curve + '.invTwist') if mc.attributeQuery('invTwist', n=curve, ex=1) else None + if lDiv and lDiv > 10: + mc.setAttr(curve + '.lengthDivisions', 10) + if wDiv and wDiv > 2: + try: + mc.setAttr(curve + '.widthDivisions', 2) + except BaseException: + pass + if twist and twist != 0: + mc.setAttr(curve + '.Twist', 0) + # if invTwist and invTwist != 0: + # mc.setAttr(curve + '.invTwist', 0) + + firstCv = '%s.cv[0]' % curve + pos = mc.pointPosition(firstCv) + om_pos = om.MPoint(pos) + + targetClosestPointNormal = utils.getClosestPointAndNormal(mesh, pos)[1] + om_sel = om.MSelectionList() + geo = selectPart(2, True, curve)[0] + om_sel.add(geo) + om_mesh = om.MFnMesh(om_sel.getDagPath(0)) + + orientation = curve + '.Orientation' + prevDiff = 360 + currentDiff = 360 + i = 0 + + guesses = [] + while (currentDiff >= angleTolerance) and (i <= N): + i += 1 + currentOrien = mc.getAttr(orientation) + + om_cardFaceNormal = om_mesh.getClosestPointAndNormal(om_pos, space=om.MSpace.kWorld)[1] + currentDiff = om_cardFaceNormal.angle(targetClosestPointNormal) * 180 / math.pi % 360 + + guesses.append((currentDiff, currentOrien)) + + if currentDiff > prevDiff: + guess = currentOrien - currentDiff + else: + guess = currentOrien + currentDiff + + prevDiff = currentDiff + nextOrien = guess + + mc.setAttr(orientation, nextOrien % 360) + + # Setting the best guess as the final orientation + finalOrientation = min(guesses, key=lambda t: t[0])[1] + mc.setAttr(orientation, finalOrientation) + + if lDiv and lDiv > 10: + mc.setAttr(curve + '.lengthDivisions', lDiv) + if wDiv and wDiv > 2: + mc.setAttr(curve + '.widthDivisions', wDiv) + if twist and twist != 0: + mc.setAttr(curve + '.Twist', twist) + # if invTwist and invTwist != 0: + # mc.setAttr(curve + '.invTwist', invTwist) + if isRefresh: + mc.refresh() + + except Exception as e: + LOGGER.exception(e) + finally: + if progress: + progress.end() + curveControlUI.updateUI() + + +# TODO: Finish this alignment function for 1.3+ + +def alignTwistToMesh(targetMesh, curve, geo): # type: (str, str, str) -> None + """Attempts to align the twist graph to the normals of the target mesh on each step point""" + if not mc.attributeQuery('Offset', n=curve, ex=1): + return + + steps = 6 - 1 # How many points to probe along the curve minus the first point + targetMeshSel = om.MSelectionList() + targetMeshSel.add(targetMesh) + fnTargetMesh = om.MFnMesh(targetMeshSel.getDagPath(0)) + + geoSel = om.MSelectionList() + geoSel.add(geo) + fnGeo = om.MFnMesh(geoSel.getDagPath(0)) + curveSel = om.MSelectionList() + curveSel.add(curve) + fnCurve = om.MFnNurbsCurve(curveSel.getDagPath(0)) + k_min, k_max = fnCurve.knotDomain + try: + warpNode = mc.ls(mc.listHistory(selectPart(2, True, curve), ac=1, il=0), typ='curveWarp')[0] + except BaseException: + return + sel = mc.ls(sl=1) + attributes.resetMultiInst(warpNode, 'twistCurve') + finalList = [] + for i in range(steps + 1): + if i == 0: + param = 0.0 + diff = 0.0 + toSet = 0.5 + else: + param = i * (k_max - k_min) / steps + pointAtParam = fnCurve.getPointAtParam(param, space=om.MSpace.kWorld) + geo_p, geo_n, _ = fnGeo.getClosestPointAndNormal(pointAtParam, space=om.MSpace.kWorld) + target_p, target_n, _ = fnTargetMesh.getClosestPointAndNormal(geo_p, space=om.MSpace.kWorld) + mc.curve(ws=1, p=[list(geo_p)[:3], list(target_p)[:3]], d=1, n='GS_DEBUG:debugCurve#') + diff = geo_n.angle(target_n) + toSet = mt.lerp(diff, 0.5, 1.0, 0, math.pi) + finalList.append((i / steps, toSet)) + mc.select(sel, r=1) + finalList.append('twistCurve') + attributes.setMultiInst(curve, finalList) + + +def changeLayersToNumbers(): + if WIDGETS['layerNumbersOnly'].isChecked(): + for i in range(10, 20): + WIDGETS['curveGrp%s' % i].changeLabel(str(i)) + else: + letter = ord('A') + letters = [chr(i) for i in range(letter, letter + 10)] + for i in range(10, 20): + WIDGETS['curveGrp%s' % i].changeLabel(letters[i - 10]) + + +def setAOSettings(silent=False): + mc.setAttr('hardwareRenderingGlobals.ssaoAmount', 0) + mc.setAttr('hardwareRenderingGlobals.ssaoRadius', 1) + mc.setAttr('hardwareRenderingGlobals.ssaoFilterRadius', 1) + mc.setAttr('hardwareRenderingGlobals.ssaoSamples', 8) + if silent: + return + MESSAGE.printInView('AO Settings Applied') + LOGGER.info('AO Amount set to: %s' % 0) + LOGGER.info('AO Radius set to: %s' % 0) + LOGGER.info('AO Filter Radius set to: %s' % 1) + LOGGER.info('AO Samples set to: %s' % 8) + + +def setTransparencySettings(type=1): + """ + Type of transparency setup: + 0 - Simple (fast, but less detailed) + 1 - Object Sorting (average performance, better accuracy) + 2 - Depth Peeling (slow, but more accurate) + """ + if type == 0: + MESSAGE.printInView('Simple Transparency Settings Applied') + mc.setAttr('hardwareRenderingGlobals.transparencyAlgorithm', 0) + elif type == 1: + MESSAGE.printInView('Object Sorting Transparency Settings Applied') + mc.setAttr('hardwareRenderingGlobals.transparencyAlgorithm', 1) + elif type == 2: + MESSAGE.printInView('Depth Peeling Transparency Settings Applied') + mc.setAttr('hardwareRenderingGlobals.transparencyAlgorithm', 3) + mc.setAttr('hardwareRenderingGlobals.transparencyQuality', 1) + mc.setAttr('hardwareRenderingGlobals.transparentShadow', 1) + LOGGER.info('Transparency Quality set to: %s' % 1) + LOGGER.info('Transparent Shadow set to: %s' % 1) + + +def alwaysOnTopToggle(*_): + if MAYA_VER >= 2022: + allCurves = [] + for i in range(80): + if mc.objExists('curveGrp_%s_Curve' % i): + curves = mc.editDisplayLayerMembers('curveGrp_%s_Curve' % i, q=1, fn=1) + if curves: + allCurves += curves + if allCurves: + firstCurveAttr = 0 + if mc.attributeQuery('alwaysDrawOnTop', n=mc.listRelatives(allCurves[0], c=1, typ='nurbsCurve')[0], ex=1): + firstCurveAttr = mc.getAttr(allCurves[0] + '.alwaysDrawOnTop') + for obj in allCurves: + shape = mc.listRelatives(obj, c=1, typ='nurbsCurve', pa=1)[0] + if mc.attributeQuery('alwaysDrawOnTop', n=shape, ex=1): + mc.setAttr(shape + '.alwaysDrawOnTop', not firstCurveAttr) + else: + setAOSettings(silent=True) + utils.AOToggle() + + +def collectionVisibilityToggle(cb): + mc.optionVar(iv=['GSCT_AutoHideCurvesOnInactiveCollections', cb]) + updateVisibilityBasedOnActiveCollection(True) + + +def updateVisibilityBasedOnActiveCollection(force=None): + cb = mc.optionVar(q='GSCT_AutoHideCurvesOnInactiveCollections') + if not cb and not force: + return + currentCollection = WIDGETS['layerCollectionsComboBox'].currentIndex() + allLayers = [x for x in mc.ls(typ='displayLayer') if ('curveGrp_' in x and '_Curve' in x)] + mainLayers = [] + nonMainLayers = [] + for layer in allLayers: + split = layer.split("_") + if len(split) == 3: + mainLayers.append(layer) + elif len(split) == 4: + nonMainLayers.append(layer) + if cb: + if currentCollection == 0: + for layer in mainLayers: + mc.setAttr(layer + '.visibility', 1) + for layer in nonMainLayers: + mc.setAttr(layer + '.visibility', 0) + else: + for layer in mainLayers: + mc.setAttr(layer + '.visibility', 0) + for layer in nonMainLayers: + if int(layer.split("_")[1]) != currentCollection: + mc.setAttr(layer + '.visibility', 0) + else: + mc.setAttr(layer + '.visibility', 1) + + +def alwaysOnTopToggleLayer(layer, *_): + if MAYA_VER >= 2022: + allCurves = [] + if mc.objExists('curveGrp_%s_Curve' % layer): + allCurves = mc.editDisplayLayerMembers('curveGrp_%s_Curve' % layer, q=1, fn=1) + if allCurves: + firstCurveAttr = 0 + if mc.attributeQuery('alwaysDrawOnTop', n=mc.listRelatives(allCurves[0], c=1, typ='nurbsCurve')[0], ex=1): + firstCurveAttr = mc.getAttr(allCurves[0] + '.alwaysDrawOnTop') + for obj in allCurves: + shape = mc.listRelatives(obj, c=1, typ='nurbsCurve', pa=1)[0] + if mc.attributeQuery('alwaysDrawOnTop', n=shape, ex=1): + mc.setAttr(shape + '.alwaysDrawOnTop', not firstCurveAttr) + else: + MESSAGE.warning("Layer is Empty") + else: + setAOSettings(silent=True) + utils.AOToggle() + + +def convertSelectionTo(targetType): + # type: (int) -> None + """ + Converts selected curves to selected targetType + 0 - Warp Card, 1 - Warp Tube, 2 - Extrude Card, 3 - Extrude Tube + """ + sel = mc.filterExpand(mc.ls(sl=1, fl=1, tr=1), sm=9) + if not sel: + MESSAGE.warning('No curves selected.') + return + + # Init + dialog = 'Skip' + compatibleCurves = [] + allCurves = [] + finalCurves = [] + + # Check if bound + sel = [obj for obj in sel if not mc.attributeQuery('Axis', n=obj, ex=1)] + if not sel: + MESSAGE.warning('No compatible curves selected.') + return + + # Check for scale factor + for obj in sel: + if not mc.attributeQuery("Orientation", n=obj, ex=1): + continue + if mc.attributeQuery("scaleFactor", n=obj, ex=1): + compatibleCurves.append(obj) + allCurves.append(obj) + + # Check for dialog result + if allCurves != compatibleCurves: + from . import ui + dialog = ui.scaleFactorConversionDialog() + if not dialog: + MESSAGE.warningInView('Operation Cancelled') + return + if dialog == 'Skip': + finalCurves = compatibleCurves + elif dialog == 'All': + finalCurves = allCurves + + # Init progress bar + progress = utils.ProgressBar("Converting Curves", len(finalCurves)) + + finalSelection = [] + for curve in sel: + if progress.tick(1): + break + + # Find components + curve = selectPart(1, True, curve)[0] + geo = selectPart(2, True, curve)[0] + grp = selectPart(0, True, curve)[0] + firstCv = '%s.cv[0]' % curve + firstCvPos = mc.pointPosition(firstCv) + firstCvVec = om.MVector(firstCvPos) + om_sel = om.MSelectionList() + om_sel.add(geo) + om_mesh = om.MFnMesh(om_sel.getDagPath(0)) + + # Get original attributes + origProfile = None + if mc.attributeQuery('Profile', n=curve, ex=1): + origProfile = mc.getAttr(curve + '.Profile') + origLattice = getLatticeValues(curve) + origAttrs = attributes.getAttr(curve) + origGraphs = attributes.getMultiInst(curve) + + # Get original material + origMaterial = utils.getShader(geo) + + # Get original layer + layer = mc.listConnections(curve, type='displayLayer') + originalLayerID = re.findall(r'\d+', layer[0])[0] + + # Get a vertex position closest to the root CV of the curve + origProfileVert, origProfileVec = utils.getMiddleVertAndNormal(curve, geo, firstCvVec) + + # Resetting attrs before orientation aligning + utils.resetAttributes(curve, geo) + _, targetVec, _ = om_mesh.getClosestPointAndNormal(om.MPoint(firstCvPos), space=om.MSpace.kWorld) + + # Duplicating curve + topGrp = mc.listRelatives(grp, p=1, pa=1) + duplicatedCurve = mc.duplicate(curve) + if topGrp: + duplicatedCurve = mc.parent(duplicatedCurve, topGrp)[0] + else: + duplicatedCurve = mc.parent(duplicatedCurve, w=1)[0] + mc.delete(grp) + + # Cleanup + mc.select(duplicatedCurve, r=1) + filteredCurves = [] + + # Convert + if targetType == 0: # Warp Card + filteredCurves = create.multiple(0, hk=True, progressBar=False, keepAttrs=False) + elif targetType == 1: # Warp Tube + filteredCurves = create.multiple(1, hk=True, progressBar=False, keepAttrs=False) + elif targetType == 2: # Extrude Card + filteredCurves = create.multiple(-2, hk=True, progressBar=False, keepAttrs=False) + elif targetType == 3: # Extrude Tube + filteredCurves = create.multiple(-1, hk=True, progressBar=False, keepAttrs=False) + finalSelection.append(filteredCurves[0]) + + # Align vars + curve = filteredCurves[0] + firstCv = '%s.cv[0]' % curve + firstCvPos = mc.pointPosition(firstCv) + firstCvVec = om.MVector(firstCvPos) + om_pos = om.MPoint(firstCvPos) + om_sel = om.MSelectionList() + geo = selectPart(2, True, curve)[0] + om_sel.add(geo) + om_mesh = om.MFnMesh(om_sel.getDagPath(0)) + + # Iterate align normals + prevDiff = 360 + currentDiff = 360 + iteration = 0 + iterLimit = 10 + tolerance = 0.1 + guesses = [] + while (currentDiff >= tolerance) and (iteration <= iterLimit): + _, faceNormal, _ = om_mesh.getClosestPointAndNormal(om_pos, space=om.MSpace.kWorld) + currentDiff = faceNormal.angle(targetVec) * 180.0 / math.pi + currentOrien = mc.getAttr(curve + '.Orientation') + + guesses.append((currentDiff, currentOrien)) + + if currentDiff > prevDiff: + guess = currentOrien - currentDiff + elif currentDiff < prevDiff: + guess = currentOrien + currentDiff + else: + guess = currentOrien + nextOrien = guess + prevDiff = currentDiff + + mc.setAttr(curve + '.Orientation', nextOrien % 360.0) + iteration += 1 + + # Setting the best guess as the final orientation + finalOrientation = min(guesses, key=lambda t: t[0])[1] + mc.setAttr(curve + '.Orientation', finalOrientation % 360.0) + + # Restore the original attributes + attributes.setAttr(curve, origAttrs, exclude=["Orientation"]) + if origGraphs: + for graph in origGraphs: + attributes.setMultiInst(curve, graph) + + # Fix Profile + if mc.attributeQuery("Profile", n=curve, ex=1) and origProfileVert and 'Profile' in origAttrs: + newProfileVert, newProfileVec = utils.getMiddleVertAndNormal(curve, selectPart(2, True, curve)[0], firstCvVec) + + # Checking if normals of the flat card are correct. If not, flip the card 180 deg. + normalCheck = origProfileVec.angle(newProfileVec) * 180.0 / math.pi + if abs(normalCheck) >= 90: + mc.setAttr(curve + ".Orientation", (mc.getAttr(curve + ".Orientation") + 180.0) % 360.0) + + # Checking if the angle between two middle vertices is correct. If not, invert the profile. + angleBetween = (firstCvVec - origProfileVert).angle(firstCvVec - newProfileVert) * 180.0 / math.pi + newProfile = origProfile * -1 if abs(angleBetween) >= 90 else origProfile + mc.setAttr(curve + ".Profile", newProfile) + + # Setting the original Lattice + if origLattice and mc.attributeQuery("Length", n=curve, ex=1): + updateLattice(utils.fromDouble2ToString(origLattice), curve) + + # Apply original material + newGeo = selectPart(2, True, curve) + if newGeo and origMaterial: + mc.sets(newGeo[0], forceElement=list(origMaterial)[0]) + + # Sort to original layers + curveAddToLayer(originalLayerID, inputCurves=[curve]) + + progress.end() + mc.select(finalSelection, r=1) + + +def toggleDynamicDivisions(): + """ + Toggles dynamic divisions on selected curves + If dynamic node set is not available - create one + """ + sel = mc.ls(sl=1, tr=1) + sel = selectPart(1, True, mc.filterExpand(sel, sm=9)) + if not sel: + return + for curve in sel: + if not mc.attributeQuery('lengthDivisions', n=curve, ex=1): + continue + divConnections = mc.listConnections(curve + '.lengthDivisions', d=1, scn=1) + tesselateNode = None + for targetNode in divConnections: + if mc.nodeType(targetNode) == 'nurbsTessellate': + tesselateNode = targetNode + break + if tesselateNode: + create.addDynamicDivisions(curve, tesselateNode) + mc.setAttr(curve + '.dynamicDivisions', 1) + else: # Just toggle the dynamic divisions functionality + mc.setAttr(curve + '.dynamicDivisions', WIDGETS['dynamicDivisions'].isChecked()) + + +def toggleAutoRefine(): + """ + Toggles automatic curve refinement + If no auto-refine node found - create one + """ + sel = mc.ls(sl=1, tr=1) + sel = selectPart(1, True, mc.filterExpand(sel, sm=9)) + if not sel: + return + for curve in sel: + if not mc.attributeQuery('Orientation', n=curve, ex=1): + continue + curveRefineConnection = mc.listConnections(curve + '.curveRefine', d=1, scn=1) + rebuildCurveNode = None + for targetNode in curveRefineConnection: + if mc.nodeType(targetNode) == 'rebuildCurve': + rebuildCurveNode = targetNode + break + if rebuildCurveNode: + newNode = create.addAutoRefine(curve, rebuildCurveNode) + mc.setAttr(curve + '.autoRefine', 1) + mc.setAttr(newNode + '.isHistoricallyInteresting', 0) + utils.deferredLp(curveControlUI.updateUI)() + else: # Just toggle the dynamic auto refine checkbox + mc.setAttr(curve + '.autoRefine', WIDGETS['autoRefine'].isChecked()) + utils.deferredLp(curveControlUI.updateUI)() diff --git a/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Bold.ttf b/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..3742457 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Bold.ttf differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Italic.ttf b/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..c9df607 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Italic.ttf differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Regular.ttf b/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..2b6392f Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/fonts/Roboto-Regular.ttf differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/drop-down-arrow.png b/Scripts/Modeling/Edit/gs_curvetools/icons/drop-down-arrow.png new file mode 100644 index 0000000..4452f34 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/drop-down-arrow.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_logo.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_logo.png new file mode 100644 index 0000000..2dda8c9 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_logo.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_reset.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_reset.png new file mode 100644 index 0000000..a72f685 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_reset.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_reset_legacy.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_reset_legacy.png new file mode 100644 index 0000000..d40254d Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_reset_legacy.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_stop.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_stop.png new file mode 100644 index 0000000..14be446 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_stop.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_stop_legacy.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_stop_legacy.png new file mode 100644 index 0000000..df5a67e Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_stop_legacy.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_ui.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_ui.png new file mode 100644 index 0000000..5e0b410 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_ui.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_ui_legacy.png b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_ui_legacy.png new file mode 100644 index 0000000..e5bc3dc Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/gsCurveToolsIcon_ui_legacy.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/marking.png b/Scripts/Modeling/Edit/gs_curvetools/icons/marking.png new file mode 100644 index 0000000..2835c8b Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/marking.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/mod.png b/Scripts/Modeling/Edit/gs_curvetools/icons/mod.png new file mode 100644 index 0000000..deb0c16 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/mod.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/reset.png b/Scripts/Modeling/Edit/gs_curvetools/icons/reset.png new file mode 100644 index 0000000..4cba961 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/reset.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/sliderLock_en_reversed.png b/Scripts/Modeling/Edit/gs_curvetools/icons/sliderLock_en_reversed.png new file mode 100644 index 0000000..8c2813c Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/sliderLock_en_reversed.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/icons/sliderLock_reversed.png b/Scripts/Modeling/Edit/gs_curvetools/icons/sliderLock_reversed.png new file mode 100644 index 0000000..638a116 Binary files /dev/null and b/Scripts/Modeling/Edit/gs_curvetools/icons/sliderLock_reversed.png differ diff --git a/Scripts/Modeling/Edit/gs_curvetools/init.py b/Scripts/Modeling/Edit/gs_curvetools/init.py new file mode 100644 index 0000000..a48273c --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/init.py @@ -0,0 +1,161 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +import csv +import io +import os +from imp import reload + +import maya.cmds as mc +import maya.mel as mel + +from .constants import * +from .utils import utils + +reload(utils) + + +def getHotkeys(): + path = os.path.dirname(os.path.realpath(__file__)) + '/utils/hotkeys.csv' + with io.open(path, 'r', encoding='utf-8') as f: + reader = csv.reader(f, delimiter='|') + return list(reader) + + +class Init: + + def __init__(self): + if mc.workspaceControl('GSCT_CurveTools', q=1, ex=1): + utils.stopUI(True) + if mc.workspaceControlState('GSCT_CurveTools', q=1, ex=1): + mc.workspaceControlState('GSCT_CurveTools', r=1) + + utils.resetOptionVars() + + # Init Runtime Command + self.delRuntimeCmds() + + # Get hotkeys from csv file + hotkeys = getHotkeys() + + for i in range(len(hotkeys)): + cmd = 'import gs_curvetools\ntry:\n ' + hotkeys[i][1] + '\nexcept:\n print("This function requires GS CurveTools.")' + commandName = 'GSCT_' + hotkeys[i][0].strip().replace(' ', '_') + if not mc.runTimeCommand(commandName, ex=1): + mc.runTimeCommand( + commandName, + c=cmd, + ann=hotkeys[i][2], + cat=hotkeys[i][3], + cl=hotkeys[i][4] + ) + else: + mc.runTimeCommand(commandName, e=1, delete=1) + mc.runTimeCommand( + commandName, + c=cmd, + ann=hotkeys[i][2], + cat=hotkeys[i][3], + cl=hotkeys[i][4] + ) + + # Shelf + folder = utils.getFolder.icons() + shelf = mel.eval('$gsTempShelfTopLevel = $gShelfTopLevel') + ui = 'import gs_curvetools.main as ct_main\nct_main.main()' + uiIcon = folder + 'gsCurveToolsIcon_ui.png' + reset = 'import gs_curvetools.utils.utils as ct_ut\nfrom imp import reload\nreload(ct_ut)\nct_ut.resetUI()' + resetIcon = folder + 'gsCurveToolsIcon_reset.png' + stop = 'import gs_curvetools.utils.utils as ct_ut\nfrom imp import reload\nreload(ct_ut)\nct_ut.stopUI()' + stopIcon = folder + 'gsCurveToolsIcon_stop.png' + if MAYA_VER <= 2018: + uiIcon = folder + 'gsCurveToolsIcon_ui_legacy.png' + resetIcon = folder + 'gsCurveToolsIcon_reset_legacy.png' + stopIcon = folder + 'gsCurveToolsIcon_stop_legacy.png' + if mc.tabLayout(shelf, ex=1): + allTabs = mc.tabLayout(shelf, q=1, tl=1) + ex = False + for ele in allTabs: + if ele == 'GS': + ex = True + break + if not ex: + mel.eval('addNewShelfTab ("GS");') + mc.tabLayout(shelf, e=1, st='GS') + buttons = mc.shelfLayout(shelf + '|GS', q=1, ca=1) + if buttons: + for button in buttons: + if mc.shelfButton(button, q=1, l=1) == 'GS_CurveTools'\ + or mc.shelfButton(button, q=1, l=1) == 'GS_CurveTools_Reset'\ + or mc.shelfButton(button, q=1, l=1) == 'GS_CurveTools_Stop': + mc.deleteUI(button, ctl=1) + mel.eval('shelfTabRefresh;') + currentShelf = mc.tabLayout(shelf, q=1, st=1) + mc.setParent(currentShelf) + mc.shelfButton(style=mc.shelfLayout(currentShelf, query=1, style=1), + sourceType="python", + label="GS_CurveTools", + width=mc.shelfLayout(currentShelf, query=1, cellWidth=1), + command=ui, + image1=uiIcon, + height=mc.shelfLayout(currentShelf, query=1, cellHeight=1), + annotation="Toggle GS CurveTools Window") + mc.shelfButton(style=mc.shelfLayout(currentShelf, query=1, style=1), + sourceType="python", + label="GS_CurveTools_Reset", + width=mc.shelfLayout(currentShelf, query=1, cellWidth=1), + command=reset, + image1=resetIcon, + height=mc.shelfLayout(currentShelf, query=1, cellHeight=1), + annotation="GS CurveTools Reset to Defaults") + mc.shelfButton(style=mc.shelfLayout(currentShelf, query=1, style=1), + sourceType="python", + label="GS_CurveTools_Stop", + width=mc.shelfLayout(currentShelf, query=1, cellWidth=1), + command=stop, + image1=stopIcon, + height=mc.shelfLayout(currentShelf, query=1, cellHeight=1), + annotation="GS CurveTools Stop Scripts") + msg = '
How To Fix Maya 2020-2022 UV Bug:
') + + linkButton = wrap.Button(row.layout()) + linkButton.setLabel('Open Fix') + linkButton.setButtonStyle('small-filled') + linkButton.clicked.connect(lambda: utils.openLink( + 'https://gs-curvetools.readthedocs.io/en/latest/faq.html#maya-2020-2022-and-broken-uvs')) + + dismissMessage = wrap.Button(row.layout()) + dismissMessage.setLabel('Dismiss') + dismissMessage.setButtonStyle('small-filled') + dismissMessage.clicked.connect(lambda: row.setHidden(True)) + dismissMessage.clicked.connect(lambda: mc.optionVar(iv=['GSCT_UVBugMessageDismissed', 1])) + + # Advanced Visibility Frame + with wrap.Frame(layout, 'advancedVisibilityFrame', label='Advanced Visibility', + margins=style.scale([3, 2, 3, 2]), spacing=style.scale(3)) as visibilityFrame: + with wrap.Row(visibilityFrame.getFrameLayout()) as row: + + geometryHighlight = wrap.Button(row.layout(), 'geometryHighlight') + geometryHighlight.setLabel('Geometry Highlight', lineHeight=100) + geometryHighlight.setCheckable(True) + geometryHighlight.setChecked(bool(mc.optionVar(q="GSCT_GeometryHighlightEnabled"))) + geometryHighlight.setButtonStyle('small') + + geometryHighlight.clicked.connect(noUndo(core.advancedVisibility.geometryHighlightCommand)) + + curveHighlight = wrap.Button(row.layout(), 'curveHighlight') + curveHighlight.setLabel('Curve Highlight', lineHeight=100) + curveHighlight.setCheckable(True) + curveHighlight.setButtonStyle('small') + curveHighlight.setChecked(False) + curveHighlight.clicked.connect(undo(core.advancedVisibility.toggleCurveHighlightFromUI)) + + visibilityFrame.getFrameLayout().addWidget(wrap.separator()) + + def sliderChangeCommand(*_): + core.advancedVisibility.applySettingsToNode() + core.advancedVisibility.saveSettingsFromUI() + + with wrap.Column(visibilityFrame.getFrameLayout(), objName='gsCurveHighlightFrame', spacing=style.scale(4)) as column: + column.setEnabled(False) # Disable immediately. We are going to check its state later. + + # CV Size + with wrap.Row(column.layout(), spacing=4) as row: + CVsizeLabel = wrap.Label() + CVsizeLabel.setLabel("CV Size:") + CVsizeLabel.setFixedWidth(style.scale(100)) + CVsizeLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + pointSizeSlider = wrap.mayaSlider( + mc.floatSliderGrp( + 'gsPointSizeSlider', + f=1, adj=2, w=1, cw=[1, 45], min=1, max=100, v=10, + dc=lambda _: noUndo(core.advancedVisibility.applySettingsToNode)(), + cc=noUndo(sliderChangeCommand), + ) + ) + WIDGETS['gsPointSizeSlider'] = pointSizeSlider + + row.layout().addWidget(CVsizeLabel, 0) + row.layout().addWidget(pointSizeSlider, 1) + + # Selected Color + with wrap.Row(column.layout(), spacing=style.scale(4)) as row: + selectedColorLabel = wrap.Label() + selectedColorLabel.setLabel("Selected Color:") + selectedColorLabel.setFixedWidth(style.scale(100)) + selectedColorLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + selectedColorPicker = wrap.ColorPicker("gsSelectedCVColor") + selectedColorPicker.connectDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + selectedColorPicker.connectCommand(noUndo(sliderChangeCommand)) + + selectedAlphaValue = wrap.FloatField("gsSelectedCVAlpha") + selectedAlphaValue.setFixedWidth(style.scale(45)) + selectedAlphaValue.setValue(1.0) + selectedAlphaValue.setRange(0, 1) + selectedAlphaValue.setStep(0.01) + selectedAlphaValue.setPrecision(2) + selectedAlphaValue.setDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + selectedAlphaValue.setReleaseCommand(noUndo(sliderChangeCommand)) + + row.layout().addWidget(selectedColorLabel, 0) + row.layout().addWidget(selectedColorPicker, 1) + row.layout().addWidget(selectedAlphaValue, 0) + + # Deselected Color + with wrap.Row(column.layout(), spacing=style.scale(4)) as row: + deselectedColorLabel = wrap.Label() + deselectedColorLabel.setLabel("Deselected Color:") + deselectedColorLabel.setFixedWidth(style.scale(100)) + deselectedColorLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + deselectedColorPicker = wrap.ColorPicker("gsDeselectedCVColor") + deselectedColorPicker.connectDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + deselectedColorPicker.connectCommand(noUndo(sliderChangeCommand)) + + deselectedAlphaValue = wrap.FloatField("gsDeselectedCVAlpha") + deselectedAlphaValue.setFixedWidth(style.scale(45)) + deselectedAlphaValue.setValue(1) + deselectedAlphaValue.setRange(0, 1) + deselectedAlphaValue.setStep(0.01) + deselectedAlphaValue.setPrecision(2) + deselectedAlphaValue.setDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + deselectedAlphaValue.setReleaseCommand(noUndo(sliderChangeCommand)) + + row.layout().addWidget(deselectedColorLabel, 0) + row.layout().addWidget(deselectedColorPicker, 1) + row.layout().addWidget(deselectedAlphaValue, 1) + + column.layout().addWidget(wrap.separator()) + + # Curve Visibility + curveVisibility = wrap.Button(column.layout(), 'curveVisibility') + curveVisibility.setLabel('Curve Visibility', lineHeight=100) + curveVisibility.setCheckable(True) + curveVisibility.setButtonStyle('small') + curveVisibility.setChecked(True) + curveVisibility.clicked.connect(noUndo(sliderChangeCommand)) + + with wrap.Row(column.layout(), spacing=4) as row: + curveWidthLabel = wrap.Label() + curveWidthLabel.setLabel("Curve Width:") + curveWidthLabel.setFixedWidth(style.scale(100)) + curveWidthLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + curveWidthSlider = wrap.mayaSlider( + mc.floatSliderGrp( + 'gsCurveWidthSlider', + f=1, adj=2, w=1, cw=[1, 45], min=1, max=100, v=4, + dc=lambda _: noUndo(core.advancedVisibility.applySettingsToNode)(), + cc=noUndo(sliderChangeCommand), + ), + ) + WIDGETS['gsCurveWidthSlider'] = curveWidthSlider + + row.layout().addWidget(curveWidthLabel, 0) + row.layout().addWidget(curveWidthSlider, 1) + + with wrap.Row(column.layout(), spacing=style.scale(4)) as row: + curveHighlightColorLabel = wrap.Label() + curveHighlightColorLabel.setLabel("Curve Color:") + curveHighlightColorLabel.setFixedWidth(style.scale(100)) + curveHighlightColorLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + curveHighlightColor = wrap.ColorPicker("gsCurveHighlightColor") + curveHighlightColor.connectDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + curveHighlightColor.connectCommand(noUndo(sliderChangeCommand)) + + curveHighlightAlpha = wrap.FloatField("gsCurveHighlightAlpha") + curveHighlightAlpha.setFixedWidth(style.scale(45)) + curveHighlightAlpha.setValue(1.0) + curveHighlightAlpha.setRange(0, 1) + curveHighlightAlpha.setStep(0.01) + curveHighlightAlpha.setPrecision(2) + curveHighlightAlpha.setDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + curveHighlightAlpha.setReleaseCommand(noUndo(sliderChangeCommand)) + + row.layout().addWidget(curveHighlightColorLabel, 0) + row.layout().addWidget(curveHighlightColor, 1) + row.layout().addWidget(curveHighlightAlpha, 1) + + column.layout().addWidget(wrap.separator()) + + # Hull Visibility + hullVisibility = wrap.Button(column.layout(), 'hullVisibility') + hullVisibility.setLabel('Hull Visibility', lineHeight=100) + hullVisibility.setCheckable(True) + hullVisibility.setButtonStyle('small') + hullVisibility.clicked.connect(noUndo(sliderChangeCommand)) + + with wrap.Row(column.layout(), spacing=4) as row: + hullWidthLabel = wrap.Label() + hullWidthLabel.setLabel("Hull Width:") + hullWidthLabel.setFixedWidth(style.scale(100)) + hullWidthLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + hullWidthSlider = wrap.mayaSlider( + mc.floatSliderGrp( + 'gsHullWidthSlider', + f=1, adj=2, w=1, cw=[1, 45], min=1, max=100, v=3, + dc=lambda _: noUndo(core.advancedVisibility.applySettingsToNode)(), + cc=noUndo(sliderChangeCommand), + ) + ) + WIDGETS['gsHullWidthSlider'] = hullWidthSlider + + row.layout().addWidget(hullWidthLabel, 0) + row.layout().addWidget(hullWidthSlider, 1) + + with wrap.Row(column.layout(), spacing=style.scale(4)) as row: + hullColorLabel = wrap.Label() + hullColorLabel.setLabel("Hull Color:") + hullColorLabel.setFixedWidth(style.scale(100)) + hullColorLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + hullColor = wrap.ColorPicker("gsHullHighlightColor") + hullColor.connectDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + hullColor.connectCommand(noUndo(sliderChangeCommand)) + + hullAlpha = wrap.FloatField("gsHullHighlightAlpha") + hullAlpha.setFixedWidth(style.scale(45)) + hullAlpha.setValue(1.0) + hullAlpha.setRange(0, 1) + hullAlpha.setStep(0.01) + hullAlpha.setPrecision(2) + hullAlpha.setDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + hullAlpha.setReleaseCommand(noUndo(sliderChangeCommand)) + + row.layout().addWidget(hullColorLabel, 0) + row.layout().addWidget(hullColor, 1) + row.layout().addWidget(hullAlpha, 1) + + column.layout().addWidget(wrap.separator()) + + with wrap.Frame(column.layout(), 'advancedVisibilityFrame', label='Other Options:', + margins=style.scale([3, 2, 3, 2]), spacing=style.scale(3)) as optionsFrame: + with wrap.Row(optionsFrame.getFrameLayout()) as row: + lazyUpdate = wrap.Button(row.layout(), 'lazyUpdate') + lazyUpdate.setLabel("Lazy Update", lineHeight=100) + lazyUpdate.setCheckable(True) + lazyUpdate.setButtonStyle('small') + lazyUpdate.clicked.connect(noUndo(sliderChangeCommand)) + + alwaysOnTop = wrap.Button(row.layout(), 'alwaysOnTop') + alwaysOnTop.setLabel("Always On Top", lineHeight=100) + alwaysOnTop.setCheckable(True) + alwaysOnTop.setButtonStyle('small') + alwaysOnTop.setChecked(True) + alwaysOnTop.clicked.connect(noUndo(sliderChangeCommand)) + + optionsFrame.getFrameLayout().addWidget(wrap.separator()) + + experimentalLabel = wrap.Label(optionsFrame.getFrameLayout()) + experimentalLabel.setLabel("Distance Colors:") + + with wrap.Row(optionsFrame.getFrameLayout()) as row: + curveDistanceColor = wrap.Button(row.layout(), 'curveDistanceColor') + curveDistanceColor.setLabel("Curve", lineHeight=100) + curveDistanceColor.setCheckable(True) + curveDistanceColor.setButtonStyle('small') + curveDistanceColor.setChecked(True) + curveDistanceColor.clicked.connect(noUndo(sliderChangeCommand)) + + cvDistanceColor = wrap.Button(row.layout(), 'cvDistanceColor') + cvDistanceColor.setLabel("CV", lineHeight=100) + cvDistanceColor.setCheckable(True) + cvDistanceColor.setButtonStyle('small') + cvDistanceColor.setChecked(True) + cvDistanceColor.clicked.connect(noUndo(sliderChangeCommand)) + + hullDistanceColor = wrap.Button(row.layout(), 'hullDistanceColor') + hullDistanceColor.setLabel("Hull", lineHeight=100) + hullDistanceColor.setCheckable(True) + hullDistanceColor.setButtonStyle('small') + hullDistanceColor.setChecked(True) + hullDistanceColor.clicked.connect(noUndo(sliderChangeCommand)) + + with wrap.Row(optionsFrame.getFrameLayout()) as row: + distanceColorMinLabel = wrap.Label() + distanceColorMinLabel.setLabel("Color Min:") + distanceColorMinLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + distanceColorMinInput = wrap.FloatField("gsDistanceColorMinValue") + distanceColorMinInput.setValue(0.25) + distanceColorMinInput.setRange(0.01, 1) + distanceColorMinInput.setStep(0.01) + distanceColorMinInput.setPrecision(2) + distanceColorMinInput.setDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + distanceColorMinInput.setReleaseCommand(noUndo(sliderChangeCommand)) + + distanceColorMaxLabel = wrap.Label() + distanceColorMaxLabel.setLabel("Color Max:") + distanceColorMaxLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + distanceColorMaxInput = wrap.FloatField("gsDistanceColorMaxValue") + distanceColorMaxInput.setValue(1.0) + distanceColorMaxInput.setRange(0.01, 1) + distanceColorMaxInput.setStep(0.01) + distanceColorMaxInput.setPrecision(2) + distanceColorMaxInput.setDragCommand(lambda *_: noUndo(core.advancedVisibility.applySettingsToNode)()) + distanceColorMaxInput.setReleaseCommand(noUndo(sliderChangeCommand)) + + row.layout().addWidget(distanceColorMinLabel, 1) + row.layout().addWidget(distanceColorMinInput, 1) + row.layout().addWidget(distanceColorMaxLabel, 1) + row.layout().addWidget(distanceColorMaxInput, 1) + + optionsFrame.getFrameLayout().addWidget(wrap.separator()) + + experimentalLabel = wrap.Label(optionsFrame.getFrameLayout()) + experimentalLabel.setLabel("Experimental (possibly slow):") + + CVocclusion = wrap.Button(optionsFrame.getFrameLayout(), 'CVocclusion') + CVocclusion.setLabel("Enable CV Occlusion", lineHeight=100) + CVocclusion.setCheckable(True) + CVocclusion.setButtonStyle('small') + CVocclusion.clicked.connect(noUndo(sliderChangeCommand)) + + with wrap.Row(optionsFrame.getFrameLayout(), spacing=style.scale(4)) as row: + selectOccluderButton = wrap.Button(row.layout(), "gsSelectOccluderButton") + selectOccluderButton.setFixedWidth(style.scale(100)) + selectOccluderButton.setLabel('Select Occluder') + selectOccluderButton.clicked.connect(noUndo(core.advancedVisibility.selectOccluderFromScene)) + + occluderMeshName = wrap.LineEdit('gsOccluderMeshName', row.layout()) + occluderMeshName.setPlaceholderText('Select or Type Occluder Mesh Name') + occluderMeshName.setClearButtonEnabled(True) + occluderMeshName.editingFinished.connect(noUndo(sliderChangeCommand)) + core.advancedVisibility.loadSettingsFromOptionVar() + tooltips.toggleCustomTooltipsCurveControl(core.getOption('enableTooltips')) + + layout.addWidget(wrap.separator()) + + resetButton = wrap.Button(layout, 'resetControlSliders') + resetButton.setLabel('Reset Sliders Range') + resetButton.clicked.connect(core.resetControlSliders) + + def twistGraphPopOut(self): + name = "GSCT_TwistGraphPopOut" + if mc.workspaceControl(name, q=1, ex=1): + mc.deleteUI(name) + popOut = CreatePopOut(name, "Twist Graph Large", 512, 400) + graph = wrap.FallOffCurve(popOut.widgetLayout, 'twistCurve_large') + + def twistGraphCommand(_): + core.attributes.propagateGraphs(graph) + core.attributes.storeGraphs(graph) + graph.changeCommand(twistGraphCommand) + with wrap.Row(popOut.widgetLayout, margins=style.scale([5, 0, 5, 2])) as row: + row.setFixedHeight(style.BUTTON_HEIGHT) + + magnitude = wrap.FloatField('Magnitude_large', row.layout(), attrName='Magnitude') + magnitude.setFixedWidth(style.scale(45)) + magnitude.setRange(-99, 99) + magnitude.setStep(0.01) + magnitude.setPrecision(2) + magnitude.setDragCommand(pa(core.sliders.curveControlSliderDrag, magnitude)) + magnitude.setReleaseCommand(core.sliders.release) + + resetButton = wrap.Button(row.layout()) + resetButton.setLabel('Reset Curve') + + def resetTwist(): + utils.resetSingleGraph('twist') + core.attributes.storeGraphs(graph) + resetButton.clicked.connect(undo(resetTwist)) + + core.curveControlUI.updateUI() + + def widthGraphPopOut(self): + name = "GSCT_WidthGraphPopOut" + if mc.workspaceControl(name, q=1, ex=1): + mc.deleteUI(name) + popOut = CreatePopOut(name, "Width Graph Large", 512, 400) + graph = wrap.FallOffCurve(popOut.widgetLayout, 'scaleCurve_large') + + def widthGraphCommand(_): + core.attributes.propagateGraphs(graph) + core.attributes.storeGraphs(graph) + graph.changeCommand(widthGraphCommand) + with wrap.Row(popOut.widgetLayout, margins=style.scale([5, 0, 5, 2])) as row: + row.setFixedHeight(style.BUTTON_HEIGHT) + resetButton = wrap.Button(row.layout()) + resetButton.setLabel('Reset Curve') + + def resetWidthCmd(): + utils.resetSingleGraph('width') + core.attributes.storeGraphs(graph) + resetButton.clicked.connect(undo(resetWidthCmd)) + core.curveControlUI.updateUI() + + def profileGraphPopOut(self): + name = "GSCT_ProfileGraphPopOut" + if mc.workspaceControl(name, q=1, ex=1): + mc.deleteUI(name) + popOut = CreatePopOut(name, "Profile Graph Large", 512, 400) + graph = wrap.FallOffCurve(popOut.widgetLayout, 'profileCurve_large', attr=False) + + def changeCommand(value): + core.updateLattice(value) + core.equalizeProfileCurve() + graph.changeCommand(changeCommand) + with wrap.Row(popOut.widgetLayout, margins=style.scale([5, 0, 5, 2])) as row: + row.setFixedHeight(style.BUTTON_HEIGHT) + resetButton = wrap.Button(row.layout()) + resetButton.setLabel('Reset Curve') + resetButton.clicked.connect(undo(core.resetProfileCurve)) + core.curveControlUI.updateUI() + + +# UV Editor Window +def uvEditorWorkspace(): + global uveditor + if mc.workspaceControl(UV_EDITOR_NAME, q=1, ex=1): + if MAYA_VER >= 2018: + if not mc.workspaceControl(UV_EDITOR_NAME, q=1, vis=1): + mc.workspaceControl(UV_EDITOR_NAME, e=1, rs=1) + else: + mc.workspaceControl(UV_EDITOR_NAME, e=1, vis=0) + try: + uveditor.updateEditor() # pylint: disable=used-before-assignment + except Exception as e: + LOGGER.exception(e) + else: + mc.workspaceControl(UV_EDITOR_NAME, e=1, fl=1) + mc.deleteUI(UV_EDITOR_NAME) + else: + uveditor = UVEditor('uveditor') + uveditor.init() + uveditor.updateEditor() + from . import main + main.checkScriptJobs(UV_EDITOR_NAME) + + +class UVEditor(QtWidgets.QWidget): + + def __init__(self, name): + super(UVEditor, self).__init__() + self.name = name + self.timer = utils.Timer() + self.mouseToggle = False + self.previousSelection = [] + self.uvUpdateCheck = 0 + self.isolateMode = False + self.currentSelection = [] + + def init(self): + if mc.workspaceControl(UV_EDITOR_NAME, q=1, ex=1): + mc.deleteUI(UV_EDITOR_NAME) + parent = mayaWorkspaceControl(name=UV_EDITOR_NAME, + label=UV_EDITOR_LABEL, + retain=False, iw=705, ih=575, + widthProperty="free") + self.setParent(parent) + + parent.layout().addWidget(self) + self.window() + + self.editor.signal_keyPress.connect(self.updateButtons) + self.editor.signal_mouseMove.connect(self._updateCurves) + self.editor.signal_mouseRelease.connect(self._stopCurvesUpdate) + self.editor.signal_uvDrawEnd.connect(self.manualCurveUpdate) + self.editor.signal_functionKeyPress.connect(undo(self.functionSwitch)) + self.uvList.signal_keyPressed.connect(self.updateVisibility) + + def window(self): + # Layout + mainLayout = QtWidgets.QVBoxLayout(self) + mainLayout.setContentsMargins(*style.scale([2, 2, 2, 2])) + + self.scrollWidget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(self.scrollWidget) + scrollArea = QtWidgets.QScrollArea() + scrollArea.setWidget(self.scrollWidget) + mainLayout.addWidget(scrollArea) + + # Layout Settings + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(style.scale(2)) + layout.setMargin(0) + layout.setAlignment(QtCore.Qt.AlignTop) + + scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + scrollArea.setWidgetResizable(True) + + # Editor Init + self.editor = uv_editor.Editor() + self.editor.init() + self.updateColors() + + # Main Widgets + with wrap.Row(layout) as mainRow: + with wrap.Column(mainRow.layout()) as mainColumn: + mainColumn.layout().setAlignment(QtCore.Qt.AlignTop) + mainColumn.setFixedWidth(style.scale(130)) + + # Controller switches + mainColumn.layout().addWidget(wrap.separator()) + controllerLabel = wrap.Label(mainColumn.layout()) + controllerLabel.setLabel('Controller Mode') + + self.controllerGroup = QtWidgets.QButtonGroup(mainColumn.layout()) + self.controllerGroup.buttonReleased.connect(self.updateControllerMode) + + with wrap.Row(mainColumn.layout()) as selectMove: + select = wrap.Button(selectMove.layout(), 'gsUVSelect') + select.setCheckable(True) + select.setChecked(True) + select.setLabel("Select (Q)") + move = wrap.Button(selectMove.layout(), 'gsUVMove') + move.setCheckable(True) + move.setLabel("Move (W)") + with wrap.Row(mainColumn.layout()) as rotateScale: + rotate = wrap.Button(rotateScale.layout(), 'gsUVRotate') + rotate.setCheckable(True) + rotate.setLabel("Rotate (E)") + scale = wrap.Button(rotateScale.layout(), 'gsUVScale') + scale.setCheckable(True) + scale.setLabel("Scale (R)") + + with wrap.Row(mainColumn.layout()) as scaleRow: + self.directionSwitch = QtWidgets.QButtonGroup(scaleRow.layout()) + self.directionSwitch.buttonReleased.connect(self.updateControllerMode) + + horizontal = wrap.Button(scaleRow.layout(), 'gsUVHorizontalScale') + horizontal.setLabel('H') + horizontal.setButtonStyle('small') + horizontal.setCheckable(True) + horizontal.setChecked(True) + vertical = wrap.Button(scaleRow.layout(), 'gsUVVerticalScale') + vertical.setButtonStyle('small') + vertical.setCheckable(True) + vertical.setLabel('V') + + self.directionSwitch.addButton(horizontal, 0) + self.directionSwitch.addButton(vertical, 1) + + drawUV = wrap.Button(mainColumn.layout(), 'gsDrawUVs') + drawUV.setCheckable(True) + drawUV.setLabel('Draw (D)') + + self.controllerGroup.addButton(select, 0) + self.controllerGroup.addButton(move, 1) + self.controllerGroup.addButton(rotate, 2) + self.controllerGroup.addButton(scale, 3) + self.controllerGroup.addButton(drawUV, 4) + + mainColumn.layout().addWidget(wrap.separator()) + + # Utility Functions + utilityLabel = wrap.Label(mainColumn.layout()) + utilityLabel.setLabel('Utility Functions') + + with wrap.Row(mainColumn.layout()) as HVFlip: + hFlipUV = wrap.Button(HVFlip.layout(), 'gsHorizontalFlipUV') + hFlipUV.setLabel('H-Flip (H)') + hFlipUV.clicked.connect(undo(self.horizontalFlipUV)) + + vFlipUV = wrap.Button(HVFlip.layout(), 'gsVerticalFlipUV') + vFlipUV.setLabel('V-Flip (V)') + vFlipUV.clicked.connect(undo(self.verticalFlipUV)) + + resetUV = wrap.Button(mainColumn.layout(), 'gsResetUVs') + resetUV.setLabel('Reset UV (X)') + resetUV.clicked.connect(undo(self.resetUVs)) + + syncSelection = wrap.Button(mainColumn.layout(), 'gsSyncSelectionUVs') + syncSelection.setLabel('Sync Selection (S)') + syncSelection.clicked.connect(undo(self.syncSelection)) + + randomizeUVs = wrap.Button(mainColumn.layout(), 'gsRandomizeUVs') + randomizeUVs.setLabel('Randomize') + randomizeUVs.setIcon('mod-bottom') + randomizeUVs.clicked.connect(undo(self.randomizeUVs)) + + mainColumn.layout().addWidget(wrap.separator()) + + # UV List + uvListLabel = wrap.Label(mainColumn.layout()) + uvListLabel.setLabel('UV List') + + self.uvList = wrap.UVItemList(mainColumn.layout()) + self.uvList.setLayout(mainColumn.layout()) + self.uvList.signal_mouseReleased.connect(self.updateVisibility) + + focusView = wrap.Button(mainColumn.layout(), 'gsFocusUVs') + focusView.setLabel('Focus View (F)') + focusView.clicked.connect(self.editor.focusView) + + with wrap.Row(mainColumn.layout()) as hideIsolate: + isolateSelect = wrap.Button(hideIsolate.layout(), 'gsUVIsolateSelect') + isolateSelect.setLabel('Isolate (I)') + isolateSelect.clicked.connect(self.isolateSelect) + + hide = wrap.Button(hideIsolate.layout(), 'gsUVHide') + hide.setLabel('Hide (O)') + hide.clicked.connect(self.hide) + + showAllButton = wrap.Button(mainColumn.layout(), 'gsUVShowAll') + showAllButton.setLabel('Show All (A)') + showAllButton.clicked.connect(self.showAll) + + mainColumn.layout().addWidget(wrap.separator()) + + with wrap.Frame(mainColumn.layout(), label="Options") as optionsFrame: + # Texture Controls + textureFunctionsLabel = wrap.Label(optionsFrame.getFrameLayout()) + textureFunctionsLabel.setLabel('Texture Controls:') + + transparency = wrap.Button(optionsFrame.getFrameLayout(), 'UVEditorTransparencyToggle') + transparency.setButtonStyle('small') + transparency.setCheckable(True) + transparency.setChecked(mc.optionVar(q='GSCT_UVEditorTransparencyToggle')) + transparency.setLabel("Transparency") + + alphaOnly = wrap.Button(optionsFrame.getFrameLayout(), 'UVEditorAlphaOnlyToggle') + alphaOnly.setButtonStyle('small') + alphaOnly.setCheckable(True) + alphaOnly.setChecked(mc.optionVar(q='GSCT_UVEditorAlphaOnly')) + alphaOnly.setLabel("Alpha Only") + + useTransforms = wrap.Button(optionsFrame.getFrameLayout(), 'UVEditorUseTransforms') + useTransforms.setButtonStyle('small') + useTransforms.setCheckable(True) + useTransforms.setChecked(True) + useTransforms.setLabel("Use Transforms") + useTransforms.clicked.connect(self.updateTexture) + + def toggleAlphaCommand(): + if transparency.isChecked(): + self.editor.toggleAlpha(True) + else: + self.editor.toggleAlpha(False) + alphaOnly.setChecked(False) + self.updateTexture() + mc.optionVar(iv=['GSCT_UVEditorTransparencyToggle', transparency.isChecked()]) + transparency.clicked.connect(toggleAlphaCommand) + + def toggleAlphaOnlyCommand(): # BUG: Find out why switching textures does not update alpha + if alphaOnly.isChecked(): + transparency.setChecked(True) + else: + transparency.setChecked(False) + toggleAlphaCommand() + mc.optionVar(iv=['GSCT_UVEditorAlphaOnly', alphaOnly.isChecked()]) + alphaOnly.clicked.connect(toggleAlphaOnlyCommand) + + optionsFrame.getFrameLayout().addWidget(wrap.separator()) + + # Editor Colors + textureFunctionsLabel = wrap.Label(optionsFrame.getFrameLayout()) + textureFunctionsLabel.setLabel('Editor Colors:') + + with wrap.Row(optionsFrame.getFrameLayout(), margins=style.scale([5, 0, 5, 5])) as colorRow: + backgroundColorPicker = wrap.ColorPicker('UVEditorBGColorPicker', colorRow.layout()) + backgroundColorPicker.setRGBColors( + utils.colorFrom255to1(eval(mc.optionVar(q="GSCT_UVEditorBGColor"))) + ) + backgroundColorPicker.connectCommand(self.updateColors) + + gridColorPicker = wrap.ColorPicker('UVEditorGridColorPicker', colorRow.layout()) + gridColorPicker.setRGBColors( + utils.colorFrom255to1(eval(mc.optionVar(q="GSCT_UVEditorGridColor"))) + ) + gridColorPicker.connectCommand(self.updateColors) + + frameColorPicker = wrap.ColorPicker('UVEditorFrameColorPicker', colorRow.layout()) + frameColorPicker.setRGBColors( + utils.colorFrom255to1(eval(mc.optionVar(q="GSCT_UVEditorFrameColor"))) + ) + frameColorPicker.connectCommand(self.updateColors) + + # UV Card Colors + cardColors = wrap.Label(optionsFrame.getFrameLayout()) + cardColors.setLabel('UV Colors:') + + with wrap.Row(optionsFrame.getFrameLayout(), margins=style.scale([5, 0, 5, 5])) as colorRow2: + selectedCardFrameColor = wrap.ColorPicker('UVEditorUVFrameSelectedColorPicker', colorRow2.layout()) + selectedCardFrameColor.setRGBColors( + utils.colorFrom255to1(eval(mc.optionVar(q="GSCT_UVEditorUVFrameSelectedColor"))) + ) + selectedCardFrameColor.connectCommand(self.updateColors) + + deselectedCardFrameColor = wrap.ColorPicker('UVEditorUVFrameDeselectedColorPicker', colorRow2.layout()) + deselectedCardFrameColor.setRGBColors( + utils.colorFrom255to1(eval(mc.optionVar(q="GSCT_UVEditorUVFrameDeselectedColor"))) + ) + deselectedCardFrameColor.connectCommand(self.updateColors) + + cardFillColor = wrap.ColorPicker('UVEditorUVCardFillColorPicker', colorRow2.layout()) + cardFillColor.setRGBColors( + utils.colorFrom255to1(eval(mc.optionVar(q="GSCT_UVEditorUVCardFillColor"))) + ) + cardFillColor.connectCommand(self.updateColors) + + mainColumn.layout().addWidget(wrap.separator()) + + mainRow.layout().addWidget(self.editor) + + if MAYA_VER in [2020, 2022] and not mc.optionVar(q='GSCT_UVBugMessageDismissed'): + with wrap.Row(mainLayout) as row: + label = wrap.Label(row.layout()) + label.setLabel('How To Fix Maya 2020-2022 UV Bug:
') + + linkButton = wrap.Button(row.layout()) + linkButton.setLabel('Open Fix') + linkButton.setButtonStyle('small-filled') + linkButton.clicked.connect(lambda: utils.openLink( + 'https://gs-curvetools.readthedocs.io/en/latest/faq.html#maya-2020-2022-and-broken-uvs') + ) + + dismissMessage = wrap.Button(row.layout()) + dismissMessage.setLabel('Dismiss Message') + dismissMessage.setButtonStyle('small-filled') + dismissMessage.clicked.connect(lambda: row.setHidden(True)) + dismissMessage.clicked.connect(lambda: mc.optionVar(iv=['GSCT_UVBugMessageDismissed', 1])) + + def updateColors(self, *_): + if self.editor: + core.saveOptions() + try: + self.editor.changeColor( + eval(mc.optionVar(q="GSCT_UVEditorBGColor")), + eval(mc.optionVar(q="GSCT_UVEditorGridColor")), + eval(mc.optionVar(q="GSCT_UVEditorFrameColor")), + eval(mc.optionVar(q="GSCT_UVEditorUVCardFillColor")), + eval(mc.optionVar(q="GSCT_UVEditorUVFrameSelectedColor")), + eval(mc.optionVar(q="GSCT_UVEditorUVFrameDeselectedColor")) + ) + except Exception as e: + self.editor.changeColor() + LOGGER.exception(e) + self.editor.update() + + @noUndo + def updateEditor(self): + uveditor.updateItemList() + uveditor.updateUVs() + uveditor.updateTexture() + + def showAll(self): + self.uvList.expandAll() + self.uvList.selectAll() + self.updateVisibility() + self.isolateMode = False + + @noUndo + def updateUVs(self, keepSelection=False): + sel = mc.filterExpand(mc.ls(sl=1, fl=1, l=1), sm=9) + oldUVs = self.editor.UVDict + self.editor.purgeUVs() + if not sel: + return + sel = self.checkForLegacyUVs(sel) + for item in sel: + if (mc.attributeQuery('Orientation', n=item, ex=1) and + mc.connectionInfo(item + '.Orientation', isSource=1)): + if (mc.attributeQuery('gsmessage', n=item, ex=1) and + mc.listConnections(item + '.gsmessage')): + curves = core.getAllConnectedCurves(item) + else: + curves = [item] + for curve in curves: + attrs = core.attributes.getUVs(curve) + cb = core.attributes.getCheckboxes(curve) + if attrs: + uv = self.editor.createUV(curve) + if 'flipUV' in cb: + uv.flip = not cb['flipUV'] + uv.moveUV( + x=attrs['moveU'], + y=attrs['moveV'], + rot=attrs['rotateUV'] * -1, + sx=attrs['scaleU'], + sy=attrs['scaleV'], + ) + if keepSelection: + newUVs = self.editor.UVDict + for uv in newUVs: + if uv in oldUVs: + newUVs[uv].setSelected(True) + + def checkForLegacyUVs(self, curves): + legacyCurves = [] + for curve in curves: + root = mc.attributeQuery('rotateRootUV', n=curve, ex=1) + tip = mc.attributeQuery('rotateTipUV', n=curve, ex=1) + if root or tip: + rootValue = mc.getAttr(curve + '.rotateRootUV') + tipValue = mc.getAttr(curve + '.rotateTipUV') + if rootValue or tipValue: + legacyCurves.append(curve) + if not legacyCurves: + return curves + else: + MESSAGE.warningInView('Non Zero Legacy UV Attributes Detected') + dialog = mc.confirmDialog( + title='Legacy UVs', + message='Non Zero Legacy UVs Detected.\nUV Editor is not compatible with them.\nZero them out to proceed?', + icon='warning', + button=['Yes', 'Cancel'], + cancelButton='Cancel', + dismissString='Cancel' + ) + if dialog == 'Yes': + for curve in legacyCurves: + mc.setAttr(curve + '.rotateRootUV', 0) + mc.setAttr(curve + '.rotateTipUV', 0) + return curves + elif dialog == 'Cancel': + dialog = mc.confirmDialog( + title='Legacy UVs', + message='UV Editor is not compatible with the old UVs.\nSome of the cards are ignored.', + icon='information', + button='OK', + cancelButton='OK', + dismissString='OK' + ) + return list(set(curves) - set(legacyCurves)) + + @noUndo + def updateTexture(self): + sel = core.selectPart(2, justReturn=True) + if not sel: + self.editor.removeTexture() + self.editor.diffusePath = '' + return + geo = mc.filterExpand(sel, sm=12) + if not geo: + return + dag = mc.ls(geo[-1], dag=1, s=1) + shader = mc.listConnections(dag, d=1, s=1, t='shadingEngine') + if not shader: + return + network = mc.hyperShade(lun=shader[0]) + fileNode = None + alphaNode = None + if network: + for node in network: + if mc.nodeType(node) == 'file' and mc.connectionInfo(node + '.outColor', isSource=1): + fileNode = node + break + for node in network: + if mc.nodeType(node) == 'file' and mc.connectionInfo(node + '.outTransparency', isSource=1): + alphaNode = node + break + if not fileNode: + return + texturePath = mc.getAttr(fileNode + '.fileTextureName') + alphaPath = mc.getAttr(alphaNode + '.fileTextureName') if alphaNode else None + + # Find place2dTexture node + place2dTexture = None + if mc.attributeQuery('coverage', n=fileNode, ex=1): + info = mc.connectionInfo(fileNode + '.coverage', sfd=1) + if info: + place2dTexture = info.split('.') + if place2dTexture: + place2dTexture = place2dTexture[0] + + # Get coverage and transformFrame from place2dTexture + cov, trans = None, None + if place2dTexture and mc.objExists(place2dTexture) and WIDGETS['UVEditorUseTransforms'].isChecked(): + if (mc.attributeQuery('coverage', ex=1, n=place2dTexture) and + mc.attributeQuery('translateFrame', ex=1, n=place2dTexture)): + try: + cov = mc.getAttr(place2dTexture + '.coverage')[0] + trans = mc.getAttr(place2dTexture + '.translateFrame')[0] + except Exception as e: + LOGGER.exception(e) + coverage = cov if cov else (1.0, 1.0) + translation = (trans[0], trans[1]) if trans and trans else (0, 0) + + if not WIDGETS['UVEditorTransparencyToggle'].isChecked() and not WIDGETS['UVEditorAlphaOnlyToggle'].isChecked(): + alphaPath = None + if texturePath: + _, ext = os.path.splitext(texturePath) + try: # Python 3 + supportedFormats = list([str(x, encoding='ASCII').lower() for x in QtGui.QImageReader.supportedImageFormats()]) + except BaseException: # Python 2 fallback + supportedFormats = list([str(x).decode('ASCII').lower() for x in QtGui.QImageReader.supportedImageFormats()]) + if ext and (ext[1:].lower() not in supportedFormats): + MESSAGE.warning( + "{} format is not supported. Use JPG/JPEG, PNG, TIF/TIFF (LZW or No Compression), TGA (24bit, no RLE)".format(ext[1:].upper())) + return + err = self.editor.setTexture('%s' % texturePath, alphaPath, coverage, translation) + if err == 'SamePath': + return + elif err == 'NoTexture': + MESSAGE.warning("Texture file could not be loaded.") + return + elif err == 'ZeroTexture': + MESSAGE.warning("Invalid Path or Zero Texture") + return + + def manualCurveUpdate(self): + try: + self._updateCurves() + except Exception as e: + LOGGER.exception(e) + finally: + self._stopCurvesUpdate() + + def _updateCurves(self): + if self.uvUpdateCheck == 0: + mc.undoInfo(ock=1, cn='gsUVUpdate') + self.uvUpdateCheck = 1 + + sel = mc.filterExpand(mc.ls(sl=1), sm=9) + uvs = self.editor.getUVs() + self.currentSelection *= 0 + + if not sel: + return + + for curve in sel: + if curve in uvs: + self.currentSelection.append(curve) + else: + if (mc.attributeQuery('gsmessage', n=curve, ex=1) and + mc.listConnections(curve + '.gsmessage')): + boundCurves = core.getAllConnectedCurves(curve) + if boundCurves: + self.currentSelection += boundCurves + + if not self.timer.increment(1.0 / 60.0): + return + if not self.currentSelection: + return + + uvs = self.editor.getUVs() + for curve in self.currentSelection: + if curve in uvs: + core.attributes.setAttr(curve, uvs[curve]) + + def _stopCurvesUpdate(self): + if self.uvUpdateCheck == 1: + mc.undoInfo(cck=1) + self.uvUpdateCheck = 0 + self.currentSelection *= 0 + core.curveControlUI.updateUI() + + def updateButtons(self, controllerMode, scaleMode): + buttons = self.controllerGroup.buttons() + direction = self.directionSwitch.buttons() + if controllerMode == 'SELECT': + buttons[0].setChecked(True) + elif controllerMode == 'MOVE': + buttons[1].setChecked(True) + elif controllerMode == 'ROTATE': + buttons[2].setChecked(True) + elif controllerMode == 'SCALE': + buttons[3].setChecked(True) + if scaleMode == 'H': + direction[0].setChecked(True) + else: + direction[1].setChecked(True) + elif controllerMode == 'DRAW': + buttons[4].setChecked(True) + + def updateControllerMode(self): + buttonID = self.controllerGroup.checkedId() + scaleID = self.directionSwitch.checkedId() + scale = 'H' + if buttonID == 0: + mode = 'SELECT' + elif buttonID == 1: + mode = 'MOVE' + elif buttonID == 2: + mode = 'ROTATE' + elif buttonID == 3: + mode = 'SCALE' + if scaleID == 0: + scale = 'H' + else: + scale = 'V' + else: + mode = 'DRAW' + self.editor.controllerModeChange(mode, scale) + + def updateItemList(self): + sel = mc.filterExpand(mc.ls(sl=1), sm=9) + if not sel: + self.uvList.clearItemList() + return + + finalDict = {} + for curve in sel: + if (mc.attributeQuery('Orientation', n=curve, ex=1) and + mc.connectionInfo(curve + '.Orientation', isSource=1)): + if (mc.attributeQuery('gsmessage', n=curve, ex=1) and + mc.listConnections(curve + '.gsmessage')): + boundCurves = core.getAllConnectedCurves(curve) + finalDict[curve] = boundCurves + else: + finalDict[curve] = [] + self.uvList.updateItemList(finalDict) + + def updateVisibility(self): + itemList = self.uvList.getSelection() + items = self.editor.getAllUVs() + for item in items: + if item.name in itemList: + item.setVisible(True) + else: + item.setVisible(False) + self.editor.update() + + def hide(self): + """Hides selected UV items""" + selUVs = self.editor.getAllUVs(selected=True) + selUVsList = [i.name for i in selUVs] + for uv in selUVs: + uv.setVisible(False) + itemList = self.uvList.getItemList() + selectList = [] + for item in itemList: + if item.curveName in selUVsList: + selectList.append(item) + self.uvList.selectItems(selectList) + + def isolateSelect(self): + allUVs = self.editor.getAllUVs() + selUVs = self.editor.getAllUVs(selected=True) + if not selUVs: + return + if self.isolateMode: + self.showAll() + self.isolateMode = False + return + self.isolateMode = True + selUVsList = [i.name for i in selUVs] + for uv in allUVs: + if uv.name not in selUVsList: + uv.setVisible(False) + itemList = self.uvList.getItemList() + deselectList = [] + for item in itemList: + if item.curveName not in selUVsList: + deselectList.append(item) + self.uvList.selectItems(deselectList) + + def horizontalFlipUV(self): + items = self.editor.getAllUVs(selected=True) + for item in items: + if item.name and mc.attributeQuery('flipUV', n=item.name, ex=1): + flip = mc.getAttr(item.name + '.flipUV') + mc.setAttr(item.name + '.flipUV', not flip) + item.flip = flip + item.update() + if items: + self.editor.update() + + def verticalFlipUV(self): + self.editor.verticalFlipUV() + self.manualCurveUpdate() + + def functionSwitch(self, key): + if key == 'H': + self.horizontalFlipUV() + elif key == 'V': + self.verticalFlipUV() + elif key == 'X': + self.resetUVs() + elif key == 'I': + self.isolateSelect() + elif key == 'O': + self.hide() + elif key == 'A': + self.showAll() + elif key == 'S': + self.syncSelection() + + def resetUVs(self): + self.editor.resetUV() + self.manualCurveUpdate() + + def randomizeUVs(self): + if utils.getMod() == 'Shift': + self.editor.randomizeUVs(True) + else: + self.editor.randomizeUVs(False) + self.manualCurveUpdate() + + def syncSelection(self): + """Selects curves in Maya Viewport based on UV Editor selection""" + sel = mc.filterExpand(mc.ls(sl=1), sm=9) + if not sel: + return + selUVs = [x.name for x in self.editor.getAllUVs(selected=True)] + newSel = [x for x in sel if x in selUVs] + if newSel: + mc.select(newSel, r=1) + + @utils.deferredLp + def x(): + uvs = self.editor.getAllUVs() + for uv in uvs: + uv.setSelected(True) + self.editor.scene().update() + x() + + +uveditor = UVEditor('uveditor') + + +# Colors Window + +class CustomLayerColors: + + def window(self, *_): + self.windowName = 'GSCT_CustomLayerColorsWindow' + if mc.workspaceControl(self.windowName, q=1, ex=1): + mc.deleteUI(self.windowName) + popOut = CreatePopOut(self.windowName, "Layers Customization", 280, 586) + + layout = popOut.widgetLayout + layout.setAlignment(QtCore.Qt.AlignTop) + + letters = [chr(l) for l in range(ord('A'), ord('A') + 10)] + + wrap.Label(layout).setLabel("Color Controls:") + + with wrap.Frame(layout, label='Gradient', margins=[2, 2, 2, 2]) as generateFrame: + + self.gradientRowsNumber = wrap.ControlSlider(generateFrame.getFrameLayout()) + self.gradientRowsNumber.setLabel('Rows') + self.gradientRowsNumber.setMinMax(1, 80) + self.gradientRowsNumber.setValue(20) + + with wrap.Row(generateFrame.getFrameLayout()) as gradientColorsLayout: + self.minGradientSwatch = wrap.ColorPicker('gradientSwatchMin', gradientColorsLayout.layout()) + self.minGradientSwatch.setHSVColors([0.1, 1, 1]) + self.maxGradientSwatch = wrap.ColorPicker('gradientSwatchMax', gradientColorsLayout.layout()) + self.maxGradientSwatch.setHSVColors([300, 1, 1]) + + generateGradient = wrap.Button(generateFrame.getFrameLayout(), 'gsGenerateLayerColorGradient') + generateGradient.setLabel('Generate Gradient') + generateGradient.clicked.connect(self.generateColorGradient) + + with wrap.Frame(layout, label='Randomize', margins=[2, 2, 2, 2]) as randomizeFrame: + + self.saturationMin = wrap.ControlSlider(randomizeFrame.getFrameLayout(), typ='float') + self.saturationMin.setMinMax(0, 1) + self.saturationMin.setValue(0.8) + self.saturationMin.setStep(0.01) + self.saturationMin.setLabel('SatMin') + + self.saturationMax = wrap.ControlSlider(randomizeFrame.getFrameLayout(), typ='float') + self.saturationMax.setMinMax(0, 1) + self.saturationMax.setValue(1.0) + self.saturationMax.setStep(0.01) + self.saturationMax.setLabel('SatMax') + + randomizeButton = wrap.Button(randomizeFrame.getFrameLayout(), 'gsRandomizeLayerColors') + randomizeButton.setLabel('Randomize All') + randomizeButton.clicked.connect(self.randomizeAllColors) + + layout.addWidget(wrap.separator()) + + wrap.Label(layout).setLabel("Layers:") + + def colorRow(parent, i): + with wrap.Row(parent) as singleColor: + singleColor.setFixedHeight(style.scale(16)) + + label = wrap.Label() + if 10 <= i < 20: + label.setLabel('%s (%s)' % (i, letters[i - 10])) + else: + label.setLabel(str(i)) + label.setFixedWidth(style.scale(int(35))) + + layerName = wrap.LineEdit('layerCustomName_%s' % i) + newFont = QtGui.QFont('Roboto') + newFont.setPointSizeF(style.scale(5)) + layerName.setFixedHeight(style.scale(18)) + layerName.setFrame(False) + layerName.setFont(newFont) + layerName.setAutoFormat(True) + layerName.setClearButtonEnabled(True) + + swatch = wrap.ColorPicker('layerColorPicker_%s' % i) + + randButton = wrap.Button() + randButton.setButtonStyle('small-filled') + randButton.setLabel('Rand', lineHeight=100) + randButton.setLabelStyle('small') + randButton.setWidthHeight(h=style.scale(10)) + randButton.clicked.connect(pa(self.randomizeColor, swatch)) + + resetButton = wrap.Button() + resetButton.setButtonStyle('small-filled') + resetButton.setLabel('Reset', lineHeight=100) + resetButton.setLabelStyle('small') + resetButton.setWidthHeight(h=style.scale(10)) + resetButton.clicked.connect(pa(self.resetColor, swatch)) + resetButton.clicked.connect(pa(self.resetName, layerName)) + + singleColor.layout().addWidget(label) + singleColor.layout().addWidget(layerName, 3) + singleColor.layout().addWidget(swatch, 1) + singleColor.layout().addWidget(randButton, 1) + singleColor.layout().addWidget(resetButton, 1) + return singleColor + + with wrap.Frame(layout, label='0-19') as frame1: + for i in range(20): + colorRow(frame1.getFrameLayout(), i) + frame1.setCollapsed(False) + with wrap.Frame(layout, label='20-39') as frame2: + for i in range(20, 40): + colorRow(frame2.getFrameLayout(), i) + frame2.setCollapsed(True) + with wrap.Frame(layout, label='40-79') as frame3: + for i in range(40, 80): + colorRow(frame3.getFrameLayout(), i) + frame3.setCollapsed(True) + + layout.addWidget(wrap.separator()) + + wrap.Label(layout).setLabel("Commands:") + + resetButton = wrap.Button(layout, 'gsResetAllLayerColors') + resetButton.setLabel('Reset All') + resetButton.clicked.connect(self.resetAll) + + with wrap.Row(layout) as controlButtons: + getCurrent = wrap.Button(controlButtons.layout(), 'gsGetCurrentSceneLayers') + getCurrent.setLabel('Get From Scene') + getCurrent.clicked.connect(self.getFromLayers) + + setAsCurrent = wrap.Button(controlButtons.layout(), 'gsSetAsCurrentSceneLayers') + setAsCurrent.setLabel('Set To Scene') + setAsCurrent.clicked.connect(self.setToLayers) + + with wrap.Row(layout) as buttonsFrame: + + loadSaved = wrap.Button(buttonsFrame.layout(), 'gsLoadGlobalLayerPreset') + loadSaved.setLabel('Load Preset') + loadSaved.clicked.connect(self.loadPreset) + + saveButton = wrap.Button(buttonsFrame.layout(), 'gsSaveGlobalLayerPreset') + saveButton.setLabel('Save As Preset') + saveButton.clicked.connect(self.savePreset) + + self.getFromLayers() + + def randomizeColor(self, swatch): + satMin = self.saturationMin.getValue() + satMax = self.saturationMax.getValue() + swatch.setRGBColors(core.toggleColor.generateBrightColor(satMin, satMax)) + + def randomizeAllColors(self): + for i in range(80): + self.randomizeColor(WIDGETS['layerColorPicker_%s' % i]) + + def resetColor(self, swatch): + swatch.setRGBColors([0, 0, 0]) + + def resetName(self, field): + field.clear() + + def resetAll(self): + for i in range(80): + WIDGETS['layerColorPicker_%s' % i].setRGBColors([0, 0, 0]) + WIDGETS['layerCustomName_%s' % i].clear() + + def generateColorGradient(self): + rows = self.gradientRowsNumber.getValue() + colorMin = self.minGradientSwatch.getHSVColors() + colorMax = self.maxGradientSwatch.getHSVColors() + for i in range(rows): + fraction = i / float(rows) + h = mt.lerp(fraction, colorMin[0], colorMax[0]) + s = mt.lerp(fraction, colorMin[1], colorMax[1]) + v = mt.lerp(fraction, colorMin[2], colorMax[2]) + WIDGETS['layerColorPicker_%s' % i].setHSVColors([h, s, v]) + + def getFromLayers(self): + # Getting colors + colorDict = core.toggleColor.readColorDict() + for key in colorDict: + WIDGETS['layerColorPicker_%s' % key].setRGBColors(colorDict[key]) + # Getting names + core.toggleColor.checkColorStorageNode() + nameDict = eval(mc.getAttr(core.toggleColor.STORAGE_NODE + '.layerName')) + for key in nameDict: + if nameDict[key]: + WIDGETS['layerCustomName_%s' % key].setText(nameDict[key]) + + def getCurrentSwatches(self): + colorDict = {} + for i in range(80): + colorDict[i] = WIDGETS['layerColorPicker_%s' % i].getRGBColors() + return colorDict + + def getCurrentNames(self): + core.toggleColor.checkColorStorageNode() + nameDict = {} + for i in range(80): + nameDict[i] = WIDGETS['layerCustomName_%s' % i].text() + return nameDict + + def setToLayers(self): + colorDict = self.getCurrentSwatches() + core.toggleColor.writeColorDict(colorDict) + if WIDGETS['colorMode'].isChecked(): + core.toggleColor.updateColors() + if WIDGETS['syncCurveColor'].isChecked(): + core.toggleColor.syncCurveColors() + core.updateMainUI() + + # Setting names + nameDict = self.getCurrentNames() + core.toggleColor.checkColorStorageNode() + mc.setAttr(core.toggleColor.STORAGE_NODE + '.layerName', str(nameDict), typ='string') + + def setCurrentSwathes(self, colorDict): + for key in colorDict: + WIDGETS['layerColorPicker_%s' % key].setRGBColors(colorDict[key]) + + def setCurrentNames(self, nameDict): + for key in nameDict: + WIDGETS['layerCustomName_%s' % key].setText(nameDict[key]) + + def savePreset(self): + mc.optionVar(sv=['gsCurveToolsCustomColors', str(self.getCurrentSwatches())]) + mc.optionVar(sv=['gsCurveToolsCustomLayerNames', str(self.getCurrentNames())]) + + def loadPreset(self): + colorString = mc.optionVar(q='gsCurveToolsCustomColors') + colorDict = eval(colorString) + self.setCurrentSwathes(colorDict) + nameString = mc.optionVar(q='gsCurveToolsCustomLayerNames') + nameDict = eval(nameString) + self.setCurrentNames(nameDict) + + +customLayerColors = CustomLayerColors() + + +class CreatePopOut(QtWidgets.QWidget): + + def __init__(self, name, label, width, height): + self.name = name + + parent = mayaWorkspaceControl(name=name, + label=label, + retain=False, iw=width, ih=height, widthProperty="free") + + super(CreatePopOut, self).__init__(parent) + + self.ui() + + parent.layout().addWidget(self) + + def ui(self): + self.mainLayout = QtWidgets.QVBoxLayout(self) + self.mainLayout.setContentsMargins(*style.scale([5, 5, 5, 5])) + + self.scrollWidget = QtWidgets.QWidget() + self.widgetLayout = QtWidgets.QVBoxLayout(self.scrollWidget) + scrollArea = QtWidgets.QScrollArea() + scrollArea.setWidget(self.scrollWidget) + self.mainLayout.addWidget(scrollArea) + + # Layout Settings + self.widgetLayout.setContentsMargins(0, 0, 0, 0) + self.widgetLayout.setSpacing(style.scale(2)) + self.widgetLayout.setMargin(0) + + scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + scrollArea.setWidgetResizable(True) + + +class About: # Creates "About" and "Contacts" windows + + def social(self): + layout = mc.columnLayout() + mc.textFieldButtonGrp( + bl='>', + bc=pa( + utils.openLink, + 'https://discord.gg/f4DH6HQ'), + l='Discord Server', + tx='https://discord.gg/f4DH6HQ') + mc.textFieldButtonGrp(bl='>', bc=pa(utils.openLink, 'https://sladkovsky3d.artstation.com/store'), + l='Online Store', tx='https://sladkovsky3d.artstation.com/store') + mc.textFieldButtonGrp(bl='>', bc=pa(utils.openLink, 'http://gs-curvetools.readthedocs.io/'), + l='Online Documentation', tx='http://gs-curvetools.readthedocs.io/') + mc.textFieldButtonGrp(bl='>', bc=pa(utils.openLink, 'https://www.twitch.tv/videonomad'), + l='Twitch Channel', tx='https://www.twitch.tv/videonomad') + mc.textFieldButtonGrp(bl='>', bc=pa(utils.openLink, 'https://www.youtube.com/GeorgeSladkovsky'), + l='YouTube Channel', tx='https://www.youtube.com/GeorgeSladkovsky') + mc.textFieldButtonGrp(bl='>', bc=pa(utils.openLink, 'https://www.artstation.com/sladkovsky3d'), + l='ArtStation Portfolio', tx='https://www.artstation.com/sladkovsky3d') + mc.textFieldButtonGrp(bl='>', bc=pa(utils.openLink, 'mailto:george.sladkovsky@gmail.com'), + l='Contact Email', tx='george.sladkovsky@gmail.com') + return layout + + def socialWindow(self): + if (mc.window('gsSocialMedia', ex=1)): + mc.deleteUI('gsSocialMedia') + mc.window('gsSocialMedia', t='Useful Links:', tlb=1) + self.social() + mc.showWindow() + + def aboutWindow(self): + if mc.window('gsAboutWindow', ex=1): + mc.deleteUI('gsAboutWindow') + mc.window('gsAboutWindow', t='About', tlb=1) + mc.rowColumnLayout(h=400, co=[1, 'left', 10], cal=([1, 'left'], [2, 'right']), nc=2, cw=[1, 200]) + mc.text(al='left', l='Version: \n%s' % core.VERSION) + mc.iconTextButton(al='right', w=90, h=80, i=utils.getFolder.icons() + 'gsCurveToolsIcon_logo.png') + mc.setParent('..') + mc.columnLayout(w=460, h=400) + mc.text(w=400, ww=1, al='left', + l='License:\nThis collection of code named GS CurveTools is a property of George Sladkovsky\ + (Yehor Sladkovskyi) and can not be copied or distributed without his written permission.\ + \n\n%s\nGeorge Sladkovsky (Yehor Sladkovskyi)\nAll Rights Reserved\ + \n\nAutodesk Maya is a property of Autodesk, Inc.\n' % core.VERSION) + mc.text(l='Social Media and Contact Links:\n') + self.social() + mc.showWindow('gsAboutWindow') + + +about = About() + + +class AttributesFilter: + + def __init__(self): + self.uiName = "GSCT_AttributesFilterPopOut" + self.attrs = [ + ('Length Divisions', 'lengthDivisions'), + ('Dynamic Divisions', 'dynamicDivisions'), + ('Width Divisions', 'widthDivisions'), + ('Orientation', 'Orientation'), + ('Twist', 'Twist'), + ('Inverted Twist', 'invTwist'), + ('Width', 'Width'), + ('WidthX', 'WidthX'), + ('WidthZ', 'WidthZ'), + ('Taper', 'Taper'), + ('Length Lock', 'LengthLock'), + ('Length', 'Length'), + ('Offset', 'Offset'), + ('Profile', 'Profile'), + ('Refine', 'curveRefine'), + ('Auto Refine', 'autoRefine'), + ('Smooth', 'curveSmooth'), + ('Normals', 'surfaceNormals'), + ('Reverse Normals', 'reverseNormals'), + ('Axis Flip', 'AxisFlip'), + ('Line Width', 'lineWidth'), + ('Sampling', 'samplingAccuracy'), + ] + + self.solidify = [ + ('Solidify', 'solidify'), + ('Solidify Thickness', 'solidifyThickness'), + ('Solidify Divisions', 'solidifyDivisions'), + ('Solidify Scale X', 'solidifyScaleX'), + ('Solidify Scale Y', 'solidifyScaleY'), + ('Solidify Offset', 'solidifyOffset'), + ('Solidify Normals', 'solidifyNormals'), + ] + + self.uvs = [ + ('Move U', 'moveU'), + ('Move V', 'moveV'), + ('Rotate UV', 'rotateUV'), + ('Scale U', 'scaleU'), + ('Scale V', 'scaleV'), + ('Flip UV', 'flipUV'), + ('Rotate Root UV', 'rotateRootUV'), + ('Rotate Tip UV', 'rotateTipUV'), + ] + + self.graphs = [ + ('Twist Graph', 'twistCurve'), + ('Scale Graph', 'scaleCurve'), + ('Profile Graph', 'profileCurve'), + ('Twist Magnitude', 'Magnitude'), + ('Profile Smoothing', 'profileSmoothing'), + ('Profile Magnitude', 'profileMagnitude'), + ] + + def openUI(self): + if mc.workspaceControl(self.uiName, q=1, ex=1): + if mc.workspaceControl(self.uiName, q=1, vis=1): + mc.deleteUI(self.uiName) + return + else: + mc.deleteUI(self.uiName) + popOut = CreatePopOut(self.uiName, "Attributes Filter", 260, 456.0) + + layout = popOut.widgetLayout + layout.setAlignment(QtCore.Qt.AlignTop) + with wrap.Row(layout, margins=style.scale([6, 0, 6, 0]), spacing=style.scale(10)) as mainRow: + mainRow.layout().setAlignment(QtCore.Qt.AlignTop) + with wrap.Column(mainRow.layout()) as leftColumn: + with wrap.Frame(leftColumn.layout(), label='Attributes', margins=[2, 0, 2, 0]) as leftFrame: + leftFrame.setCollapsible(False) + leftFrame.getFrameLayout().setAlignment(QtCore.Qt.AlignTop) + for i in range(len(self.attrs)): + btn = wrap.Button(leftFrame.getFrameLayout(), 'gsFilter_%s' % self.attrs[i][1]) + btn.setLabel(self.attrs[i][0]) + btn.setButtonStyle('small') + btn.setCheckable(True) + btn.setChecked(False if self.attrs[i][0] == 'Orientation' else True) + with wrap.Column(mainRow.layout()) as rightColumn: + rightColumn.layout().setAlignment(QtCore.Qt.AlignTop) + with wrap.Frame(rightColumn.layout(), label='Solidify', margins=[2, 0, 2, 0]) as rightFrame1: + rightFrame1.setCollapsible(False) + rightFrame1.getFrameLayout().setAlignment(QtCore.Qt.AlignTop) + for i in range(len(self.solidify)): + btn = wrap.Button(rightFrame1.getFrameLayout(), 'gsFilter_%s' % self.solidify[i][1]) + btn.setLabel(self.solidify[i][0]) + btn.setButtonStyle('small') + btn.setCheckable(True) + btn.setChecked(True) + with wrap.Frame(rightColumn.layout(), label='Graphs', margins=[2, 0, 2, 0]) as rightFrame2: + rightFrame2.setCollapsible(False) + rightFrame2.getFrameLayout().setAlignment(QtCore.Qt.AlignTop) + for i in range(len(self.graphs)): + btn = wrap.Button(rightFrame2.getFrameLayout(), 'gsFilter_%s' % self.graphs[i][1]) + btn.setLabel(list(self.graphs)[i][0]) + btn.setButtonStyle('small') + btn.setCheckable(True) + btn.setChecked(True) + with wrap.Frame(rightColumn.layout(), label='UVs', margins=[2, 0, 2, 0]) as rightFrame3: + rightFrame3.setCollapsible(False) + rightFrame3.getFrameLayout().setAlignment(QtCore.Qt.AlignTop) + for i in range(len(self.uvs)): + btn = wrap.Button(rightFrame3.getFrameLayout(), 'gsFilter_%s' % self.uvs[i][1]) + btn.setLabel(list(self.uvs)[i][0]) + btn.setButtonStyle('small') + btn.setCheckable(True) + btn.setChecked(True) + + layout.addWidget(wrap.separator()) + + with wrap.Row(layout) as confirmCancelLayout: + btn = wrap.Button(confirmCancelLayout.layout()) + btn.setLabel('Save') + btn.clicked.connect(self.saveToOptionVar) + btn.clicked.connect(lambda: MESSAGE.printInView("Filters Saved")) + btn = wrap.Button(confirmCancelLayout.layout()) + btn.setLabel('Close') + btn.clicked.connect(self.closeUI) + + self.updateUI() + self.saveToOptionVar() + + def closeUI(self): + if mc.workspaceControl(self.uiName, q=1, ex=1): + mc.deleteUI(self.uiName) + + def updateUI(self): + if mc.workspaceControl(self.uiName, q=1, ex=1): + controlsDict = self.getFromOptionVar() + if controlsDict and isinstance(controlsDict, dict): + for key in controlsDict: + buttonName = 'gsFilter_%s' % key + WIDGETS[buttonName].setChecked(controlsDict[key]) + + def saveToOptionVar(self): + if mc.workspaceControl(self.uiName, q=1, ex=1): + allControls = self.attrs + self.graphs + self.uvs + self.solidify + controlsDict = {} + for control in allControls: + buttonName = 'gsFilter_%s' % control[1] + button = WIDGETS[buttonName] if buttonName in WIDGETS else None + if button: + controlsDict.update({control[1]: button.isChecked()}) + mc.optionVar(sv=('GSCT_AttributesFilter', str(controlsDict))) + + def getFromOptionVar(self): + if mc.optionVar(ex='GSCT_AttributesFilter'): + dictString = mc.optionVar(q='GSCT_AttributesFilter') + if dictString: + return dict(eval(dictString)) + + +attributesFilter = AttributesFilter() + + +class CardToCurveWindow: + + def __init__(self): + self.uiName = "GSCT_CardToCurvePopOut" + self.buttonsDict = {} + + def openUI(self): + if mc.workspaceControl(self.uiName, q=1, ex=1): + if mc.workspaceControl(self.uiName, q=1, vis=1): + mc.deleteUI(self.uiName) + return + else: + mc.deleteUI(self.uiName) + + self.buttonsDict = self.loadClickedButtons() + self.getBtn = lambda name: self.buttonsDict[name] if name in self.buttonsDict else True + + popOut = CreatePopOut(self.uiName, "Card to Curve", 245, 271) + layout = popOut.widgetLayout + layout.setAlignment(QtCore.Qt.AlignTop) + self.mainButtonGroup = QtWidgets.QButtonGroup() + self.mainButtonGroup.setExclusive(False) + self.mainButtonGroup.buttonClicked.connect(self.saveButtonsState) + with wrap.Column(layout) as mainColumn: + mainColumn.layout().setAlignment(QtCore.Qt.AlignTop) + + with wrap.Frame(mainColumn.layout(), label='Output Type:', objName='gsCardToCurve_outputTypeSwitch') as outputTypeFrame: + outputTypeFrame.setCollapsible(False) + outputTypeFrame.setCollapsed(False) + + with wrap.Row(outputTypeFrame.getFrameLayout()) as outputTypeSwitch: + self.outputTypeGroup = QtWidgets.QButtonGroup() + WIDGETS['gsCardToCurve_cardTypeGroup'] = self.outputTypeGroup + + generateCards = wrap.Button(outputTypeSwitch.layout(), 'gsCardToCurve_generateCards') + generateCards.setButtonStyle('small') + generateCards.setLabel('Cards') + generateCards.setCheckable(True) + + generateCurves = wrap.Button(outputTypeSwitch.layout(), 'gsCardToCurve_generateCurves') + generateCurves.setButtonStyle('small') + generateCurves.setLabel('Curves Only') + generateCurves.setCheckable(True) + + self.outputTypeGroup.addButton(generateCards, 0) + self.outputTypeGroup.addButton(generateCurves, 1) + generateCards.setChecked(not mc.optionVar(q='GSCT_CardToCurveOutputType')) + generateCurves.setChecked(mc.optionVar(q='GSCT_CardToCurveOutputType')) + self.outputTypeGroup.buttonClicked.connect(self.updateActiveButtons) + + with wrap.Frame(mainColumn.layout(), label='Card Type:', objName="gsCardToCurve_cardType") as cardTypeFrame: + self.cardTypeFrame = cardTypeFrame + cardTypeFrame.setCollapsible(False) + cardTypeFrame.setCollapsed(False) + + with wrap.Row(cardTypeFrame.getFrameLayout()) as outputTypeSwitch: + self.cardTypeGroup = QtWidgets.QButtonGroup() + WIDGETS['gsCardToCurve_cardTypeGroup'] = self.cardTypeGroup + + self.warpCards = wrap.Button(outputTypeSwitch.layout(), 'gsCardToCurve_warp') + self.warpCards.setButtonStyle('small') + self.warpCards.setLabel('Warp') + self.warpCards.setCheckable(True) + + extrudeCards = wrap.Button(outputTypeSwitch.layout(), 'gsCardToCurve_extrude') + extrudeCards.setButtonStyle('small') + extrudeCards.setLabel('Extrude') + extrudeCards.setCheckable(True) + + self.cardTypeGroup.addButton(self.warpCards, 0) + self.cardTypeGroup.addButton(extrudeCards, 1) + self.warpCards.setChecked(not mc.optionVar(q='GSCT_CardToCurveCardType')) + extrudeCards.setChecked(mc.optionVar(q='GSCT_CardToCurveCardType')) + self.cardTypeGroup.buttonClicked.connect(self.updateActiveButtons) + + with wrap.Frame(mainColumn.layout(), label='Match Attributes:', objName="gsCardToCurve_matchAttributes") as matchAttributeFrame: + self.matchAttributeFrame = matchAttributeFrame + matchAttributeFrame.setCollapsible(False) + matchAttributeFrame.setCollapsed(False) + + with wrap.Row(matchAttributeFrame.getFrameLayout()) as row: + orientation = wrap.Button(row.layout(), 'gsCardToCurve_orientation') + orientation.setButtonStyle('small') + orientation.setLabel('Orientation') + orientation.setCheckable(True) + orientation.setChecked(self.getBtn('gsCardToCurve_orientation')) + + width = wrap.Button(row.layout(), 'gsCardToCurve_width') + width.setButtonStyle('small') + width.setLabel('Width') + width.setCheckable(True) + width.setChecked(self.getBtn('gsCardToCurve_width')) + + self.mainButtonGroup.addButton(orientation) + self.mainButtonGroup.addButton(width) + + with wrap.Row(matchAttributeFrame.getFrameLayout()) as row: + taper = wrap.Button(row.layout(), 'gsCardToCurve_taper') + taper.setButtonStyle('small') + taper.setLabel('Taper') + taper.setCheckable(True) + taper.setChecked(self.getBtn('gsCardToCurve_taper')) + + twist = wrap.Button(row.layout(), 'gsCardToCurve_twist') + twist.setButtonStyle('small') + twist.setLabel('Twist') + twist.setCheckable(True) + twist.setChecked(self.getBtn('gsCardToCurve_twist')) + + self.mainButtonGroup.addButton(taper) + self.mainButtonGroup.addButton(twist) + + with wrap.Row(matchAttributeFrame.getFrameLayout()) as row: + profile = wrap.Button(row.layout(), 'gsCardToCurve_profile') + profile.setButtonStyle('small') + profile.setLabel('Profile') + profile.setCheckable(True) + profile.setChecked(self.getBtn('gsCardToCurve_profile')) + + material = wrap.Button(row.layout(), 'gsCardToCurve_material') + material.setButtonStyle('small') + material.setLabel('Material') + material.setCheckable(True) + material.setChecked(self.getBtn('gsCardToCurve_material')) + + self.mainButtonGroup.addButton(profile) + self.mainButtonGroup.addButton(material) + + UVs = wrap.Button(matchAttributeFrame.getFrameLayout(), 'gsCardToCurve_UVs') + UVs.setButtonStyle('small') + UVs.setLabel('UVs') + UVs.setCheckable(True) + UVs.setChecked(self.getBtn('gsCardToCurve_UVs')) + UVs.clicked.connect(self.updateActiveButtons) + self.mainButtonGroup.addButton(UVs) + + with wrap.Frame(mainColumn.layout(), label='UV Match Options:', objName="gsCardToCurve_UVMatchOptions") as uvMatchOptionsFrame: + self.uvMatchOptionsFrame = uvMatchOptionsFrame + uvMatchOptionsFrame.setCollapsible(False) + uvMatchOptionsFrame.setCollapsed(False) + + with wrap.Row(uvMatchOptionsFrame.getFrameLayout()) as row: + verticalFlip = wrap.Button(row.layout(), 'gsCardToCurve_verticalFlip') + verticalFlip.setButtonStyle('small') + verticalFlip.setLabel('Vertical Flip') + verticalFlip.setCheckable(True) + verticalFlip.setChecked(self.getBtn('gsCardToCurve_verticalFlip')) + + horizontalFlip = wrap.Button(row.layout(), 'gsCardToCurve_horizontalFlip') + horizontalFlip.setButtonStyle('small') + horizontalFlip.setLabel('Horizontal Flip') + horizontalFlip.setCheckable(True) + horizontalFlip.setChecked(self.getBtn('gsCardToCurve_horizontalFlip')) + + self.mainButtonGroup.addButton(verticalFlip) + self.mainButtonGroup.addButton(horizontalFlip) + + with wrap.Frame(mainColumn.layout(), label='Other:') as otherOptionsFrame: + otherOptionsFrame.setCollapsible(False) + otherOptionsFrame.setCollapsed(False) + reverseCurve = wrap.Button(otherOptionsFrame.getFrameLayout(), 'gsCardToCurve_reverseCurve') + reverseCurve.setButtonStyle('small') + reverseCurve.setLabel('Reverse Curve') + reverseCurve.setCheckable(True) + reverseCurve.setChecked(self.getBtn('gsCardToCurve_reverseCurve')) + + self.mainButtonGroup.addButton(reverseCurve) + + mainColumn.layout().addWidget(wrap.separator()) + with wrap.Row(mainColumn.layout(), margins=style.scale([0, 3, 0, 3])) as _: + pass + mainColumn.layout().addWidget(wrap.separator()) + with wrap.Row(mainColumn.layout()) as row: + convertSelected = wrap.Button(row.layout(), objName="gsCardToCurve_convertSelected") + convertSelected.setLabel('Convert Selected') + convertSelected.clicked.connect(undo(core.cardToCurve)) + + cancel = wrap.Button(row.layout()) + cancel.setLabel('Cancel') + cancel.clicked.connect(self.closeUI) + + self.updateActiveButtons() + self.saveButtonsState() + + def closeUI(self): + self.saveButtonsState() + if mc.workspaceControl(self.uiName, q=1, ex=1): + if mc.workspaceControl(self.uiName, q=1, vis=1): + mc.deleteUI(self.uiName) + else: + mc.deleteUI(self.uiName) + + def updateActiveButtons(self): + if self.outputTypeGroup.checkedId() == 1: + self.matchAttributeFrame.setEnabled(False) + self.uvMatchOptionsFrame.setEnabled(False) + self.cardTypeFrame.setEnabled(False) + mc.optionVar(iv=['GSCT_CardToCurveOutputType', 1]) + else: + mc.optionVar(iv=['GSCT_CardToCurveOutputType', 0]) + mc.optionVar(iv=['GSCT_CardToCurveCardType', int(not self.warpCards.isChecked())]) + self.matchAttributeFrame.setEnabled(True) + self.cardTypeFrame.setEnabled(True) + if WIDGETS['gsCardToCurve_UVs'].isChecked(): + self.uvMatchOptionsFrame.setEnabled(True) + else: + self.uvMatchOptionsFrame.setEnabled(False) + + def saveButtonsState(self): + buttons = self.mainButtonGroup.buttons() + buttonsDict = {} + for b in buttons: + buttonsDict.update({b.objName: b.isChecked()}) + mc.optionVar(sv=['GSCT_CardToCurveOptions', str(buttonsDict)]) + + def loadClickedButtons(self): + return eval(mc.optionVar(q='GSCT_CardToCurveOptions')) + + +cardToCurveWindow = CardToCurveWindow() + + +def scaleFactorWindow(): + windowName = SCALE_FACTOR_UI + if mc.workspaceControl(windowName, q=1, ex=1): + mc.deleteUI(windowName) + popOut = CreatePopOut(windowName, "Scale Factor", 300, 95) + + layout = popOut.widgetLayout + + scaleFactor = core.getScaleFactor() + m_slider = mc.floatSliderGrp('GSCT_scaleFactorSlider', + pre=3, ss=0.01, min=0.01, max=10, fmn=0.001, fmx=1000, f=1, + v=scaleFactor + ) + + wrap.MayaSlider(m_slider, layout=layout) + + with wrap.Row(layout) as row: + saveButton = wrap.Button(row.layout()) + saveButton.setLabel('Save') + saveButton.clicked.connect(pa(core.saveScaleFactor, windowName, False)) + saveButton.clicked.connect(updateScaleFactorWindow) + saveAndCloseButton = wrap.Button(row.layout()) + saveAndCloseButton.setLabel('Save & Close') + saveAndCloseButton.clicked.connect(pa(core.saveScaleFactor, windowName)) + cancelButton = wrap.Button(row.layout()) + cancelButton.setLabel('Cancel') + cancelButton.clicked.connect(lambda: mc.deleteUI(windowName)) + + layout.addWidget(wrap.separator()) + + with wrap.Row(layout) as row: + with wrap.Row(row.layout(), margins=style.scale([2, 0, 2, 0])) as globalRow: + label = wrap.Label(globalRow.layout()) + label.setLabel("Global:") + globalValue = mc.optionVar(q=('GSCT_globalScaleFactor')) + field = wrap.LineEdit("scaleFactorGlobalValue", globalRow.layout()) + field.setText(str(globalValue)) + field.setEnabled(False) + with wrap.Row(row.layout(), margins=style.scale([2, 0, 2, 0])) as sceneRow: + label = wrap.Label(sceneRow.layout()) + label.setLabel("Scene:") + if mc.objExists('gsScaleFactorStorageNode') and mc.attributeQuery('scaleFactor', n='gsScaleFactorStorageNode', ex=1): + sceneValue = mc.getAttr('gsScaleFactorStorageNode.scaleFactor') + else: + sceneValue = "####" + field = wrap.LineEdit("scaleFactorSceneValue", sceneRow.layout()) + field.setText(str(sceneValue)) + field.setEnabled(False) + + with wrap.Row(row.layout(), margins=style.scale([2, 0, 2, 0])) as sceneRow: + label = wrap.Label(sceneRow.layout()) + label.setLabel("Selected:") + if mc.objExists('gsScaleFactorStorageNode') and mc.attributeQuery('scaleFactor', n='gsScaleFactorStorageNode', ex=1): + sceneValue = mc.getAttr('gsScaleFactorStorageNode.scaleFactor') + else: + sceneValue = "####" + field = wrap.LineEdit("scaleFactorSelectedValue", sceneRow.layout()) + field.setText(str(sceneValue)) + field.setEnabled(False) + + from . import main + main.checkScriptJobs(SCALE_FACTOR_UI) + + +def updateScaleFactorWindow(): + WIDGETS["scaleFactorGlobalValue"].setText(str(mc.optionVar(q=('GSCT_globalScaleFactor')))) + if mc.objExists('gsScaleFactorStorageNode') and mc.attributeQuery('scaleFactor', n='gsScaleFactorStorageNode', ex=1): + sceneValue = mc.getAttr('gsScaleFactorStorageNode.scaleFactor') + else: + sceneValue = "####" + WIDGETS["scaleFactorSceneValue"].setText(str(sceneValue)) + + +def scaleFactorConversionDialog(): + MESSAGE.warningInView('Curves Without Scale Factor Detected') + dialog = mc.confirmDialog( + title='No Scale Factor', + message='Curves without scale factor detected\nThose Curves were created before v1.2.7 and might not convert correctly', + icon='warning', + button=['Skip Old Curves', 'Convert All', 'Cancel'], + cancelButton='Cancel', + dismissString='Cancel' + ) + if dialog == 'Cancel': + return None + if dialog == 'Skip Old Curves': + return 'Skip' + if 'Convert All': + return 'All' + + +def curveThicknessWindow(): + name = 'GSCT_CurveThicknessWindow' + if mc.workspaceControl(name, q=1, ex=1): + mc.deleteUI(name) + popOut = CreatePopOut(name, "Curve Thickness", 300, 65) + + layout = popOut.widgetLayout + + layout.setAlignment(QtCore.Qt.AlignTop) + + m_slider = mc.floatSliderGrp('GSCT_curveThicknessSlider', + pre=3, ss=0.01, min=-1, max=10, fmn=0.001, fmx=1000, f=1, + v=mc.optionVar(q='GSCT_globalCurveThickness') + ) + + wrap.MayaSlider(m_slider, layout=layout) + + with wrap.Row(layout, margins=style.scale([0, 5, 0, 0])) as row: + def save(): + mc.optionVar(fv=('GSCT_globalCurveThickness', mc.floatSliderGrp('GSCT_curveThicknessSlider', q=1, v=1))) + mc.deleteUI(name) + + def update(): + mc.optionVar(fv=('GSCT_globalCurveThickness', mc.floatSliderGrp('GSCT_curveThicknessSlider', q=1, v=1))) + core.updateLayerThickness() + saveButton = wrap.Button(row.layout()) + saveButton.setLabel('Save') + saveButton.clicked.connect(save) + updateCurves = wrap.Button(row.layout()) + updateCurves.setLabel('Update Curves') + updateCurves.clicked.connect(update) + cancelButton = wrap.Button(row.layout()) + cancelButton.setLabel('Cancel') + cancelButton.clicked.connect(lambda: mc.deleteUI(name)) + + +def randomizeCurveWindow(): + name = "GSCT_RandomizeCurvePopOut" + if mc.workspaceControl(name, q=1, ex=1): + if mc.workspaceControl(name, q=1, vis=1): + mc.deleteUI(name) + return + else: + mc.deleteUI(name) + popOut = CreatePopOut(name, "Randomize Curve", 270, 565) + + layout = popOut.widgetLayout + layout.setAlignment(QtCore.Qt.AlignTop) + + def release(slider, *_): + core.sliders.randSliderDrag(slider) + core.sliders.randSliderRelease(slider) + core.sliders.release() + # Control Points + with wrap.Frame(layout, label='Control Points') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider0') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveCVsRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + with wrap.Row(frame.getFrameLayout()) as row: + lockFirst = wrap.Button(row.layout(), 'gsLockFirstCV') + lockFirst.setLabel('Lock First CV', lineHeight=100) + lockFirst.setButtonStyle('small') + lockFirst.setCheckable(True) + + lockLast = wrap.Button(row.layout(), 'gsLockLastCV') + lockLast.setLabel('Lock Last CV', lineHeight=100) + lockLast.setButtonStyle('small') + lockLast.setCheckable(True) + + with wrap.Row(frame.getFrameLayout()) as row: + axisX = wrap.Button(row.layout(), 'gsRandAxisX') + axisX.setLabel('X', lineHeight=100) + axisX.setButtonStyle('small') + axisX.setCheckable(True) + + axisY = wrap.Button(row.layout(), 'gsRandAxisY') + axisY.setLabel('Y', lineHeight=100) + axisY.setButtonStyle('small') + axisY.setCheckable(True) + + axisZ = wrap.Button(row.layout(), 'gsRandAxisZ') + axisZ.setLabel('Z', lineHeight=100) + axisZ.setButtonStyle('small') + axisZ.setCheckable(True) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveCVsRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 0), + cc=pa(release, 0), + ), + layout=frame.getFrameLayout() + ) + + # Rotation + with wrap.Frame(layout, label='Rotation') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider1') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveRotationRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + with wrap.Row(frame.getFrameLayout()) as row: + axisX = wrap.Button(row.layout(), 'gsRandRotateAxisX') + axisX.setLabel('X', lineHeight=100) + axisX.setButtonStyle('small') + axisX.setCheckable(True) + + axisY = wrap.Button(row.layout(), 'gsRandRotateAxisY') + axisY.setLabel('Y', lineHeight=100) + axisY.setButtonStyle('small') + axisY.setCheckable(True) + + axisZ = wrap.Button(row.layout(), 'gsRandRotateAxisZ') + axisZ.setLabel('Z', lineHeight=100) + axisZ.setButtonStyle('small') + axisZ.setCheckable(True) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveRotationRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 1), + cc=pa(release, 1), + ), + layout=frame.getFrameLayout() + ) + + # Orientation + with wrap.Frame(layout, label='Orientation') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider2') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveOrientationRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveOrientationRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 2), + cc=pa(release, 2), + ), + layout=frame.getFrameLayout() + ) + + # Twist + with wrap.Frame(layout, label='Twist') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider3') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveTwistRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveTwistRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 3), + cc=pa(release, 3), + ), + layout=frame.getFrameLayout() + ) + + # Width + with wrap.Frame(layout, label='Width') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider4') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveWidthRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + uniform = wrap.Button(frame.getFrameLayout(), 'gsWidthCheckBoxUniform') + uniform.setButtonStyle('small') + uniform.setLabel('Uniform', lineHeight=100) + uniform.setCheckable(True) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveWidthRand', min=0.001, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 4), + cc=pa(release, 4), + ), + layout=frame.getFrameLayout() + ) + + # Taper + with wrap.Frame(layout, label='Taper') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider5') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveTaperRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveTaperRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 5), + cc=pa(release, 5), + ), + layout=frame.getFrameLayout() + ) + + # Profile + with wrap.Frame(layout, label='Profile') as frame: + frame.setCollapsible(False) + with wrap.Row(frame.getFrameLayout()) as row: + enable = wrap.Button(row.layout(), 'curveRandomizeSlider6') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + slider = wrap.mayaSlider(mc.floatSliderGrp('gsCurveProfileRandMulti', l='Mult:', + min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)])) + row.layout().addWidget(slider) + + uniform = wrap.Button(frame.getFrameLayout(), 'gsProfileCheckBoxNegative') + uniform.setButtonStyle('small') + uniform.setLabel('Allow Negative Values', lineHeight=100) + uniform.setCheckable(True) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveProfileRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 6), + cc=pa(release, 6), + ), + layout=frame.getFrameLayout() + ) + + # Selection + with wrap.Frame(layout, label='Selection') as frame: + frame.setCollapsible(False) + + enable = wrap.Button(frame.getFrameLayout(), 'curveRandomizeSlider7') + enable.setLabel('Enabled', lineHeight=100) + enable.setButtonStyle('small') + enable.setCheckable(True) + + wrap.MayaSlider( + mc.floatSliderGrp( + 'gsCurveSelectRand', min=0, max=1, step=0.05, + dc=pa(core.sliders.randSliderDrag, 7), + cc=pa(release, 7), + ), + layout=frame.getFrameLayout() + ) + + layout.addWidget(wrap.separator()) + + with wrap.Row(layout) as row: + def randomizeClick(): + core.sliders.randSliderDrag(-1) + core.sliders.randSliderRelease(-1) + core.sliders.release() + randomize = wrap.Button(row.layout()) + randomize.setLabel("Randomize") + randomize.clicked.connect(randomizeClick) + + close = wrap.Button(row.layout()) + close.setLabel("Close") + close.clicked.connect(lambda: mc.deleteUI(name)) diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/__init__.py b/Scripts/Modeling/Edit/gs_curvetools/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/gs_math.py b/Scripts/Modeling/Edit/gs_curvetools/utils/gs_math.py new file mode 100644 index 0000000..691b0f1 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/gs_math.py @@ -0,0 +1,62 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +from PySide2 import QtGui + + +def lerp(x, y0, y1, x0=0, x1=1): + """ Returns the value between y0 and y1 based on x in range of x0 and x1 + + https://en.wikipedia.org/wiki/Linear_interpolation""" + return float((y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0)) + + +def quad(y1, y2, y3, fPoint, x1=0, x2=0.5, x3=1): + """ Takes three points (y1, y2, y3) and returns a point on computed curve with fPoint """ + A = (x2 * (y1 - y3) + x3 * (y2 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3)) + B = (y2 - y1) / (x2 - x1) - A * (x1 + x2) + C = y1 + return (A * (fPoint**2)) + (B * fPoint) + C + + +def dot(v1, v2): + """ Dot product of two QVector2D """ + return QtGui.QVector2D.dotProduct(v1, v2) + + +def projectPoint(A, B, P): + """ Project a point P onto a line made of two points A and B """ + A = QtGui.QVector2D(A) + B = QtGui.QVector2D(B) + P = QtGui.QVector2D(P) + AP = A - P + AB = A - B + return (A - dot(AP, AB) / dot(AB, AB) * AB).toPointF() + + +def angleDiff(a1, a2): + """ Find an absolute angle difference within 360 degrees""" + a1 = a1 % 360 + a2 = a2 % 360 + return abs(a1) - abs(a2) diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/hotkeys.csv b/Scripts/Modeling/Edit/gs_curvetools/utils/hotkeys.csv new file mode 100644 index 0000000..6d7a1ce --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/hotkeys.csv @@ -0,0 +1,79 @@ +Show Hide UI|import gs_curvetools.main as ct_main;ct_main.main()|Show/Hide UI|GS.GS_CurveTools.UI|python +Reset to Defaults|import gs_curvetools.utils.utils as ct_utils;from imp import reload;reload(ct_utils);ct_utils.resetUI()|Reset the UI to Defaults|GS.GS_CurveTools.UI|python +Stop UI|import gs_curvetools.utils.utils as ct_utils;from imp import reload;reload(ct_utils);ct_utils.stopUI()|Close the UI and Stop Scripts|GS.GS_CurveTools.UI|python +New Card|gs_curvetools.core.create.new(0)|Create New Card|GS.GS_CurveTools.Create|python +New Tube|gs_curvetools.core.create.new(1)|Create New Tube|GS.GS_CurveTools.Create|python +Curve Card|gs_curvetools.core.create.multiple(0)|Convert to Card|GS.GS_CurveTools.Create|python +Curve Tube|gs_curvetools.core.create.multiple(1)|Convert to Tube|GS.GS_CurveTools.Create|python +Bind|gs_curvetools.core.create.bind(1)|Bind Curves or Geo|GS.GS_CurveTools.Create|python +Bind Duplicate|gs_curvetools.core.create.bind(2)|Bind Curves or Geo with duplication|GS.GS_CurveTools.Create|python +Unbind|gs_curvetools.core.create.unbind()|Unbind Curves|GS.GS_CurveTools.Create|python +Add Card|gs_curvetools.core.create.populate(0,1)|Add Cards between selection|GS.GS_CurveTools.Create|python +Add Card No Blend|gs_curvetools.core.create.populate(0,2)|Add Cards between selection without attribute blend|GS.GS_CurveTools.Create|python +Add Tube|gs_curvetools.core.create.populate(1,1)|Add Tubes between selection|GS.GS_CurveTools.Create|python +Add Tube No Blend|gs_curvetools.core.create.populate(1,2)|Add Tubes between selection without attribute blend|GS.GS_CurveTools.Create|python +Fill|gs_curvetools.core.create.fill(1)|Duplicate Fill between selection|GS.GS_CurveTools.Create|python +Fill No Blend|gs_curvetools.core.create.fill(2)|Duplicate Fill between selection without attribute blend|GS.GS_CurveTools.Create|python +Subdivide|gs_curvetools.core.subdivideCurve(1)|Subdivide Selected Card/Tube|GS.GS_CurveTools.Create|python +Subdivide Duplicate|gs_curvetools.core.subdivideCurve(2)|Subdivide Selected Card/Tube and Duplicate|GS.GS_CurveTools.Create|python +Edge to Curve|gs_curvetools.core.edgeToCurve()|Convert Edges to Curves|GS.GS_CurveTools.Create|python +Card to Curve|gs_curvetools.core.cardToCurve()|Convert Cards to Curves|GS.GS_CurveTools.Create|python +Add Layer Collection|gs_curvetools.core.layerCollections.createLayerCollection()|Adds a New Layer Collection With the Specified Name|GS.GS_CurveTools.LayerCollections|python +Delete Layer Collection|gs_curvetools.core.layerCollections.deleteLayerCollection()|Deletes Current Layer Collection|GS.GS_CurveTools.LayerCollections|python +Clear Layer Collection|gs_curvetools.core.layerCollections.clear()|Deletes All The Curves from the Current Layer Collection|GS.GS_CurveTools.LayerCollections|python +Rename Layer Collection|gs_curvetools.core.layerCollections.rename()|Renames the Current Layer Collection|GS.GS_CurveTools.LayerCollections|python +Merge Up Layer Collection|gs_curvetools.core.layerCollections.mergeUp()|Merges the Current Layer Collection With One Above It|GS.GS_CurveTools.LayerCollections|python +Merge Down Layer Collection|gs_curvetools.core.layerCollections.mergeDown()|Merges the Current Layer Collection With One Below It|GS.GS_CurveTools.LayerCollections|python +Move Up Layer Collection|gs_curvetools.core.layerCollections.moveUp()|Moves the Current Layer Collection Up One Index|GS.GS_CurveTools.LayerCollections|python +Move Down Layer Collection|gs_curvetools.core.layerCollections.moveDown()|Moves the Current Layer Collection Down One Index|GS.GS_CurveTools.LayerCollections|python +Copy Layer Collection|gs_curvetools.core.layerCollections.copy()|Copies Curves from Current Layer Collection|GS.GS_CurveTools.LayerCollections|python +Paste Layer Collection|gs_curvetools.core.layerCollections.paste()|Pastes Copied Curves to Current Layer Collection|GS.GS_CurveTools.LayerCollections|python +Filter All Show|gs_curvetools.core.layersFilterToggle(True, True, hotkey=True)|Show All Layers|GS.GS_CurveTools.Layers|python +Filter All Show All Collections|"gs_curvetools.core.layersFilterToggle(True, True, hotkey=True, mod=""Ctrl"")"|Show All Layers in All Collections|GS.GS_CurveTools.Layers|python +Filter All Hide|gs_curvetools.core.layersFilterToggle(False, False, hotkey=True)|Hide All Layers|GS.GS_CurveTools.Layers|python +Filter All Hide All Collections|"gs_curvetools.core.layersFilterToggle(True, True, hotkey=True, mod=""Shift+Ctrl"")"|Hide All Layers in All Collections|GS.GS_CurveTools.Layers|python +Filter Curve|gs_curvetools.core.layersFilterToggle(True, False, hotkey=True)|Show Only Curve Component|GS.GS_CurveTools.Layers|python +Filter Curve in All Collections|"gs_curvetools.core.layersFilterToggle(True, False, hotkey=True, mod=""Ctrl"")"|Show Only Curve Component in All Collections|GS.GS_CurveTools.Layers|python +Toggle Always on Top|gs_curvetools.core.alwaysOnTopToggle()|Toggle Always on Top Mode for Curves|GS.GS_CurveTools.Layers|python +Filter Geo|gs_curvetools.core.layersFilterToggle(False, True, hotkey=True)|Show Only Geo Component|GS.GS_CurveTools.Layers|python +Filter Geo in All Collections|"gs_curvetools.core.layersFilterToggle(False, True, hotkey=True, mod=""Ctrl"")"|Show Only Geo Component in All Collections|GS.GS_CurveTools.Layers|python +Toggle Color|gs_curvetools.core.toggleColor.toggleColorVis()|Toggle Color Mode|GS.GS_CurveTools.Layers|python +Extract Selected|"gs_curvetools.core.extractSelectedCurves(""Shift"", hotkey=True)"|Extract Selected Geo|GS.GS_CurveTools.Layers|python +Extract Selected Medged|gs_curvetools.core.extractSelectedCurves(hotkey=True)|Extract Selected Geo Merged|GS.GS_CurveTools.Layers|python +Extract All|"gs_curvetools.core.extractAllCurves(""Shift"", hotkey=True)"|Extract All Geo|GS.GS_CurveTools.Layers|python +Extract All Medged|gs_curvetools.core.extractAllCurves(hotkey=True)|Extract All Geo Merged|GS.GS_CurveTools.Layers|python +Select Curve|gs_curvetools.core.selectPart(1)|Select Curve Component|GS.GS_CurveTools.Selection|python +Select Geo|gs_curvetools.core.selectPart(2)|Select Geo Component|GS.GS_CurveTools.Selection|python +Select Group|gs_curvetools.core.selectPart(0)|Select Group|GS.GS_CurveTools.Selection|python +Group Curves|gs_curvetools.core.groupCurves()|Group Selected Curves|GS.GS_CurveTools.Selection|python +Regroup by Layer|gs_curvetools.core.regroupByLayer()|Regroup using Layers|GS.GS_CurveTools.Selection|python +Transfer Attr Forward|gs_curvetools.core.attributes.transferAttr(1)|Transfer Attributes Forward|GS.GS_CurveTools.Utilities|python +Transfer Attr Backwards|gs_curvetools.core.attributes.transferAttr(2)|Transfer Attributes Backwards|GS.GS_CurveTools.Utilities|python +Copy Attributes|gs_curvetools.core.attributes.copyAttributes()|Copy attributes from selected curves|GS.GS_CurveTools.Utilities|python +Paste Attributes|gs_curvetools.core.attributes.pasteAttributes()|Paste attributes to selected curves|GS.GS_CurveTools.Utilities|python +Transfer UVs Forward|gs_curvetools.core.attributes.transferUV(1)|Transfer UVs Forward|GS.GS_CurveTools.Utilities|python +Transfer UVs Backwards|gs_curvetools.core.attributes.transferUV(2)|Transfer UVs Backwards|GS.GS_CurveTools.Utilities|python +Copy UVs|gs_curvetools.core.attributes.copyUVs()|Copy UVs from selected curves|GS.GS_CurveTools.Utilities|python +Paste UVs|gs_curvetools.core.attributes.pasteUVs()|Paste UVs to selected curves|GS.GS_CurveTools.Utilities|python +Reset Pivot to Root|gs_curvetools.core.resetCurvePivotPoint(1)|Reset Pivot Point to Root|GS.GS_CurveTools.Utilities|python +Reset Pivot to Tip|gs_curvetools.core.resetCurvePivotPoint(2)|Reset Pivot Point to Tip|GS.GS_CurveTools.Utilities|python +Rebuild Curves|gs_curvetools.core.sliders.rebuildButtonClicked()|Rebuild Selected Curves|GS.GS_CurveTools.Utilities|python +Duplicate|gs_curvetools.core.duplicateCurve()|Duplicate Selected Curves|GS.GS_CurveTools.Utilities|python +Delete Curves|gs_curvetools.core.deleteSelectedCurves()|Delete selected curves and objects|GS.GS_CurveTools.Utilities|python +Extend|gs_curvetools.core.extendCurve()|Extend Curves|GS.GS_CurveTools.Utilities|python +Reduce|gs_curvetools.core.reduceCurve()|Reduce Curves|GS.GS_CurveTools.Utilities|python +Smooth|gs_curvetools.core.smoothCurve()|Smooth Curves|GS.GS_CurveTools.Utilities|python +Orient to Normal|gs_curvetools.core.orientToFaceNormals()|Orients Selected Curve to Scalp Normals|GS.GS_CurveTools.Utilities|python +Mirror X|gs_curvetools.core.mirrorHair(0)|Mirror on X Axis|GS.GS_CurveTools.Mirror|python +Mirror Y|gs_curvetools.core.mirrorHair(1)|Mirror on Y Axis|GS.GS_CurveTools.Mirror|python +Mirror Z|gs_curvetools.core.mirrorHair(2)|Mirror on Z Axis|GS.GS_CurveTools.Mirror|python +Flip X|gs_curvetools.core.mirrorHair(0,1)|Flip on X Axis|GS.GS_CurveTools.Mirror|python +Flip Y|gs_curvetools.core.mirrorHair(1,1)|Flip on Y Axis|GS.GS_CurveTools.Mirror|python +Flip Z|gs_curvetools.core.mirrorHair(2,1)|Flip on Z Axis|GS.GS_CurveTools.Mirror|python +Add Control Curve|gs_curvetools.core.controlCurveCreate()|Add Control Curve|GS.GS_CurveTools.Control|python +Apply Control Cuve|gs_curvetools.core.controlCurveApply()|Apply Control Curve|GS.GS_CurveTools.Control|python +Curve Control Window|gs_curvetools.ui.curveControlWorkspace()|Open Curve Control Window|GS.GS_CurveTools.Control|python +UV Editor Window|gs_curvetools.ui.uvEditorWorkspace()|Open UV Editor Window|GS.GS_CurveTools.Control|python +AO Toggle|gs_curvetools.utils.utils.AOToggle()|Toggle Viewport AO|GS.GS_CurveTools.Misc|python +Advanced Visibility Toggle|gs_curvetools.core.advancedVisibility.toggleCurveHighlightFromUI(True)|Toggle Advanced Visibility|GS.GS_CurveTools.Misc|python +Geometry Highlight Toggle|gs_curvetools.core.advancedVisibility.geometryHighlightCommand()|Toggle Geometry Highlight|GS.GS_CurveTools.Misc|python \ No newline at end of file diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/style.py b/Scripts/Modeling/Edit/gs_curvetools/utils/style.py new file mode 100644 index 0000000..af200a0 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/style.py @@ -0,0 +1,786 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +import math +from imp import reload + +import maya.cmds as mc + +from . import utils + +reload(utils) + +# Scaling + +MULT = 1 +if mc.about(mac=True): + MULT = 1 +else: + MULT = mc.mayaDpiSetting(q=1, rsv=1) + # mult = 1 + + +def scale(a): + # types (int\float) -> (float) + if isinstance(a, list): + return [i * MULT for i in a] + else: + return a * MULT + + +# Fonts +NORMAL_FONT = 11 * MULT +SMALL_FONT = 10 * MULT + +# Normal Button +BUTTON_HEIGHT = 24 * MULT +BORDER_RADIUS = 4 * MULT +BORDER_WIDTH = 2 * MULT +BACKGROUND_COLOR = 'rgb(93,93,93)' +PRESSED_BACKGROUND_COLOR = 'rgb(128,128,128)' +HOVER_BACKGROUND_COLOR = 'rgb(100,100,100)' + +# Active Button (Orange) +ORANGE = 'rgb(219,148,86)' +ORANGE_HOVER = 'rgb(225,155,90)' +ORANGE_PRESSED = 'rgb(235,170,105)' + +# Active Button (Blue) +BLUE = 'rgb(82,133,166)' +BLUE_HOVER = 'rgb(95,145,185)' + +# Active Button (White) +BORDER_WIDTH_WHITE = 2 * MULT + +# Small Button +SMALL_BORDER_RADIUS = 4 * MULT +SMALL_BORDER_WIDTH = 1 * MULT +SMALL_BORDER_COLOR = 'rgb(93,93,93)' +SMALL_BACKGROUND_COLOR = 'rgba(93,93,93,0)' +SMALL_PRESSED_COLOR = 'rgba(128,128,128,255)' +SMALL_HOVER_COLOR = 'rgb(100,100,100)' + +# Layer/Set Button +LAYER_BOTTOM_PADDING = 1 * MULT +LAYER_BORDER_RADIUS = 4 * MULT +LAYER_HEIGHT = 16 * MULT +LAYER_BORDER_WIDTH = 1 * MULT + +# Images/Icons +DROP_DOWN_ARROW_IMAGE = utils.getFolder.icons() + 'drop-down-arrow.png' + +### Regular Buttons ### + +buttonNormal = ''' + QPushButton {{ + background-color: {bg_clr}; + border-radius: {br}; + min-height: {h}px; + max-height: {h}px; + }} + QPushButton:pressed {{ + background-color: {pr_bg_clr}; + }} + QPushButton:checked {{ + background-color: {ac_bg_clr}; + }} + QPushButton:hover {{ + background-color: {h_bg_clr}; + }} +}} +'''.format(br=int(BORDER_RADIUS), + h=int(BUTTON_HEIGHT), + bg_clr=str(BACKGROUND_COLOR), + pr_bg_clr=str(PRESSED_BACKGROUND_COLOR), + h_bg_clr=str(HOVER_BACKGROUND_COLOR), + ac_bg_clr=BLUE) + +buttonNormalBlueBorder = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-radius: {br}; + border-color: {borderBlue}; + border-style: solid; + border-width: {bw}px; + padding: -{bw}px; + min-height: {h}px; + max-height: {h}px; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:checked +{{ + background-color: {ac_bg_clr}; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +'''.format(br=int(BORDER_RADIUS), + h=int(BUTTON_HEIGHT), + bw=int(BORDER_WIDTH), + bg_clr=str(BACKGROUND_COLOR), + pr_bg_clr=str(PRESSED_BACKGROUND_COLOR), + h_bg_clr=str(HOVER_BACKGROUND_COLOR), + borderBlue=str(BLUE), + ac_bg_clr=BLUE) + +buttonIcon = ''' + QPushButton + {{ + background-color: none; + border-radius: {br}px; + border:none; + }} + QPushButton:checked + {{ + background-color: none; + border:none; + }} + QPushButton:pressed + {{ + background-color: {pr_bg_clr}; + }} + QPushButton:checked:pressed + {{ + background-color: {pr_bg_clr}; + }} + QPushButton:checked:hover + {{ + background-color: {h_bg_clr}; + }} + QPushButton:hover + {{ + background-color: {h_bg_clr}; + }} +}} +'''.format(br=int(BORDER_RADIUS / 2), + h=int(BUTTON_HEIGHT), + bg_clr=str(BACKGROUND_COLOR), + pr_bg_clr=str(PRESSED_BACKGROUND_COLOR), + h_bg_clr=str(HOVER_BACKGROUND_COLOR), + ac_bg_clr=BLUE) + +buttonActiveOrange = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-radius: {br}px; + min-height: {h}px; + max-height: {h}px; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +'''.format(br=int(BORDER_RADIUS), + h=int(BUTTON_HEIGHT), + bg_clr=ORANGE, + pr_bg_clr=str(ORANGE_PRESSED), + h_bg_clr=str(ORANGE_HOVER), + ac_bg_clr=BLUE) + +buttonActiveBlue = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-radius: {br}px; + min-height: {h}px; + max-height: {h}px; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +'''.format(br=int(BORDER_RADIUS), + h=int(BUTTON_HEIGHT), + bg_clr=BLUE, + pr_bg_clr=str(PRESSED_BACKGROUND_COLOR), + h_bg_clr=str(HOVER_BACKGROUND_COLOR), + ac_bg_clr=BLUE) + +buttonActiveWhite = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-radius: {br}px; + border-width: {bw}px; + border-color: white; + border-style: solid; + padding: -{bw}px; + min-height: {h}px; + max-height: {h}px; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:hover +{{ + background-color: {bg_clr}; +}} +'''.format(br=int(BORDER_RADIUS), + h=int(BUTTON_HEIGHT), + bw=int(BORDER_WIDTH_WHITE), + bg_clr=ORANGE, + pr_bg_clr=str(PRESSED_BACKGROUND_COLOR)) + +frameButton = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-width: {bw}px; + border-radius: {br}px; + border-color: {br_clr}; + border-style: solid; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:checked +{{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; + border-top-left-radius: {br}px; + border-top-right-radius: {br}px; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; +}} +QPushButton:disabled +{{ + border-style: none; +}} +QPushButton:disabled:checked +{{ + background-color: {bg_clr}; + border-style: none; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +}} +'''.format(br=4 * MULT, + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + ac_bg_clr=BLUE) + +frameButtonNotCollapsable = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-style: solid; + border-width: {bw}px; + border-top-left-radius: {br}px; + border-top-right-radius: {br}px; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; +}} +'''.format(br=4 * MULT, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=BACKGROUND_COLOR) + +smallNormal = ''' + QPushButton {{ + background-color: {bg_clr}; + border-width: {bw}px; + border-radius: {br}px; + border-color: {br_clr}; + border-style: solid; + }} + QPushButton:pressed {{ + background-color: {pr_bg_clr}; + }} + QPushButton:checked {{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; + }} + QPushButton:disabled {{ + border-style: none; + }} + QPushButton:disabled:checked {{ + background-color: {bg_clr}; + border-style: none; + }} + QPushButton:hover {{ + background-color: {h_bg_clr}; + }} + QPushButton:checked:hover {{ + background-color: {h_bg_checked_clr}; + }} +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=SMALL_BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + h_bg_checked_clr=BLUE_HOVER, + ac_bg_clr=BLUE) + +smallFilled = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-width: {bw}px; + border-radius: {br}px; + border-color: {br_clr}; + border-style: solid; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:checked +{{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; +}} +QPushButton:disabled +{{ + border-style: none; +}} +QPushButton:disabled:checked +{{ + background-color: {bg_clr}; + border-style: none; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + ac_bg_clr=BLUE) + +smallFilledBlue = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-width: {bw}px; + border-radius: {br}px; + border-color: {br_clr}; + border-style: solid; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:checked +{{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; +}} +QPushButton:disabled +{{ + border-style: none; +}} +QPushButton:disabled:checked +{{ + background-color: {bg_clr}; + border-style: none; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=BLUE, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=BLUE, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=BLUE_HOVER, + ac_bg_clr=BLUE) + +smallNoBorder = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-width: 0px; + border-radius: {br}px; + border-color: {br_clr}; + border-style: solid; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:checked +{{ + color: rgb(255,0,0) + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; +}} +QPushButton:disabled +{{ + color: rgb(255,0,0); + border-style: none; +}} +QPushButton:disabled:checked +{{ + background-color: {bg_clr}; + border-style: none; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=SMALL_BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + ac_bg_clr=BLUE) + +sliderLabel = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-width: 0px; + border-radius: {br}px; + border-color: {br_clr}; + border-style: solid; +}} +QPushButton:pressed +{{ + background-color: {bg_clr}; +}} +QPushButton:checked +{{ + color: rgb(255,0,0) + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; +}} +QPushButton:disabled +{{ + color: rgb(255,0,0); + border-style: none; +}} +QPushButton:disabled:checked +{{ + background-color: {bg_clr}; + border-style: none; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=SMALL_BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + ac_bg_clr=BLUE) + +### Compound Buttons ### + +smallCompoundTopLeft = ''' + QPushButton {{ + background-color: {bg_clr}; + border-width: {bw}px; + border-top-left-radius: {br}px; + border-top-right-radius: 0px; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + border-color: {br_clr}; + border-style: solid; + }} + QPushButton:pressed {{ + background-color: {pr_bg_clr}; + }} + QPushButton:checked {{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; + }} + QPushButton:disabled {{ + border-style: none; + }} + QPushButton:disabled:checked {{ + background-color: {bg_clr}; + border-style: none; + }} + QPushButton:hover {{ + background-color: {h_bg_clr}; + }} + QPushButton:checked:hover {{ + background-color: {h_bg_checked_clr}; + }} +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=SMALL_BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + h_bg_checked_clr=BLUE_HOVER, + ac_bg_clr=BLUE) + +smallCompoundTopRight = ''' + QPushButton {{ + background-color: {bg_clr}; + border-width: {bw}px; + border-top-left-radius: 0px; + border-top-right-radius: {br}px; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + border-color: {br_clr}; + border-style: solid; + }} + QPushButton:pressed {{ + background-color: {pr_bg_clr}; + }} + QPushButton:checked {{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; + }} + QPushButton:disabled {{ + border-style: none; + }} + QPushButton:disabled:checked {{ + background-color: {bg_clr}; + border-style: none; + }} + QPushButton:hover {{ + background-color: {h_bg_clr}; + }} + QPushButton:checked:hover {{ + background-color: {h_bg_checked_clr}; + }} +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=SMALL_BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + h_bg_checked_clr=BLUE_HOVER, + ac_bg_clr=BLUE) + +smallFilledCompoundBottom = ''' +QPushButton +{{ + background-color: {bg_clr}; + border-width: {bw}px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-bottom-left-radius: {br}px; + border-bottom-right-radius: {br}px; + border-color: {br_clr}; + border-style: solid; +}} +QPushButton:pressed +{{ + background-color: {pr_bg_clr}; +}} +QPushButton:checked +{{ + background-color: {ac_bg_clr}; + border-color: lightgrey; + border-style: solid; + border-width: {bw}px; +}} +QPushButton:disabled +{{ + border-style: none; +}} +QPushButton:disabled:checked +{{ + background-color: {bg_clr}; + border-style: none; +}} +QPushButton:hover +{{ + background-color: {h_bg_clr}; +}} +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bw=int(SMALL_BORDER_WIDTH), + bg_clr=BACKGROUND_COLOR, + pr_bg_clr=SMALL_PRESSED_COLOR, + h_bg_clr=SMALL_HOVER_COLOR, + ac_bg_clr=BLUE) + +### Layer Button ### + + +def layer(background_color='rbga(0,0,0,0)', outline='rgb(93,93,93)'): + + styleSheet = ''' + QPushButton + {{ + background-color: {bg}; + border-width: {bw}px; + border-color: {bc}; + border-style: solid; + min-height: {h}px; + max-height: {h}px; + padding: -{bw}px; + }} + QPushButton:hover + {{ + border-width: {bw}px; + border-color: {bc_hover}; + border-style: solid; + }} + QPushButton:pressed + {{ + border-width: {bw}px; + border-color: {bc_checked}; + border-style: solid; + }} + QPushButton:checked + {{ + background-color: {bg}; + border-width: {bw}px; + border-color: {bc_checked}; + border-radius: {radius}; + border-style: solid; + min-height: {h}px; + max-height: {h}px; + padding: -{bw}px; + }} + QPushButton:checked:hover + {{ + border-width: {bw}px; + border-color: {bc_checked}; + border-style: solid; + }} + '''.format(bw=math.ceil(LAYER_BORDER_WIDTH), + h=math.ceil(LAYER_HEIGHT), + bg=background_color, + bc=outline, + bc_hover=ORANGE, + radius=BORDER_RADIUS, + bc_checked='white') + + return styleSheet + +### Labels ### + + +subDLabel = ''' +QLabel +{{ + color : {orange}; +}} +'''.format(orange=ORANGE) + +layerLabel = ''' +QLabel +{{ + font: {weight} {f_size}px; + padding-bottom: {p}px; +}} +'''.format(f_size=int(NORMAL_FONT), + weight='bold', + p=int(LAYER_BOTTOM_PADDING)) + +### Input Fields ### + +inputField = ''' +QSpinBox +{ + border-style: solid; + border-radius: 4px; +} +''' + +### Combo Box ### + +smallComboBox = ''' +QComboBox +{{ + background-color: {bg_clr}; + border-radius: {br}px; +}} +QComboBox:hover +{{ + background-color: {bc_hover}; +}} +QComboBox:on +{{ + border-top-left-radius: {br}px; + border-top-right-radius: {br}px; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; +}} +QComboBox::drop-down +{{ + background-color: {bg_clr}; + border-radius: {br}px; + width: {drop_down_width}; +}} +QComboBox::down-arrow +{{ + image: url("{path}"); + width: {icon_size}px; + height: {icon_size}px; +}} +'''.format(br=int(SMALL_BORDER_RADIUS), + br_clr=SMALL_BORDER_COLOR, + bg_clr=BACKGROUND_COLOR, + bw=int(SMALL_BORDER_WIDTH), + path=DROP_DOWN_ARROW_IMAGE, + icon_size=int(scale(8)), + bc_hover=HOVER_BACKGROUND_COLOR, + drop_down_width=int(scale(16))) diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/tooltips.md b/Scripts/Modeling/Edit/gs_curvetools/utils/tooltips.md new file mode 100644 index 0000000..79f10e7 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/tooltips.md @@ -0,0 +1,888 @@ + + + + + +# importCurves +Imports Curves that were Exported using the Export Button. +NOTE: Only import files that were exported using Export Curves button. +WARNING: This operation is NOT undoable! + +# exportCurves +Exports selected curves into a .curves (or .ma) file. Can then be Imported using Import Curves. + +# changeScaleFactor +Opens a window that controls the Scale Factor. +Scale factor determines the initial scale of the created curve and adjusts other parameters. +Scale factor is stored in a global preset, in the scene and on each curve. +Priority of scale factors Curve>Scene>Global. +NOTE: Using the correct scale factor can help with the control of the curves and helps in avoiding bugs due to very large or very small scenes. + +# globalCurveThickness +Opens a window that controls the thickness of the curves in the scene as well as the global curve thickness preset across the scenes. + +# setAOSettings +Manually sets the recommended AO settings for older Maya versions. +These AO settings are needed to use the "See Through" or "Toggle Always on Top" functions. +Are applied automatically by those functions. + +# setTransparencySettings +Sets recommended transparency settings for Maya viewport. +*** +Simple Transparency is fast but very inaccurate render mode. Only suitable for simple, one layer hair. +Object Sorting Transparency has average performance impact and quality. Can have issues on complex multi-layered grooms. +Depth Transparency - these are the optimal settings for the highest quality of the hair cards preview. Can have performance impact on slower systems. + +# convertToWarpCard +Converts selection to Warp Cards. +Compatible attributes are retained. + +# convertToWarpTube +Converts selection to Warp Tubes. +Compatible attributes are retained. + +# convertToExtrudeCard +Converts selection to Extrude Cards. +Compatible attributes are retained. + +# convertToExtrudeTube +Converts selection to Extrude Tubes. +Compatible attributes are retained. + +# syncCurveColor +Toggles the syncing of the curve color to the layer color. + +# colorizedRegroup +Toggles the colorization of the regrouped layers when Regroup by Layer function is used. + +# colorOnlyDiffuse +Colorize only the diffuse component of the card material. +Alpha will stay the same. + +# checkerPattern +Toggles the use of the checker pattern when the Color mode is enabled. + +# ignoreLastLayer +Toggles the filtering (All, Curve, Geo) and Extraction (Extract All) of the last layer. If enabled, the last layer is ignored. +NOTE: Last layer is typically used for templates, so it is ignored by default. + +# ignoreTemplateCollections +Ignores the filtering (All, Curve, Geo) and Extraction (Extract All) of all the collections that have "template" in their name. Case insensitive. + +# groupTemplateCollections +Collections that have "template" in their name (case insensitive) will be grouped together into "CT_Templates" group by Regroup By Layer function. + +# syncOutlinerLayerVis +Syncs the outliner and layer visibility. If enabled, hiding the layer will also hide the curve groups in the outliner. + +# keepCurveAttributes +If enabled, the attributes that are stored in the curve will be restored if the curve that was duplicated (on its own, without the geo) is used to create other cards or tubes. +If disabled, the attributes will be ignored and reset. +Example: +1. Create a card and change its twist value. +2. Ctrl+D and Shift+P the curve (not the curve group). +3. Click Curve Card and the twist value will be restored on a newly created card. + +# boundCurvesFollowParent +Will ensure that moving a parent curve in a Bound Object (Bound Group) will also move all the child curves along with it to a new layer. Recommended to keep this enabled. + +# massBindOption +Will bind selected hair clump (or geometry) to all selected "empty" curves. + +# bindDuplicatesCurves +Will automatically duplicate the curves before binding them to the curve, leaving old curves behind with no edits. + +# bindFlipUVs +Enabling this option will flip the UVs of the original cards before binding them to the curve. +It will also automatically flip the bound geo and rotate it 90 deg. +This option will also flip the UVs back when using Unbind for better workflow. +Disabling this option will result in an old Bind/Unbind behaviour. + +# populateBlendAttributes +Enables blending of the attributes when using Add Cards/Tubes or Fill functions. + +# convertInstances +Will automatically convert instanced curves to normal curves before any other function is applied. + +# replacingCurveLayerSelection +Will disable additive selection for the layers. When holding Ctrl and clicking on a new layer, old layer will be deselected automatically. + +# useAutoRefineOnNewCurves +Automatically enables auto-refine on the new curves. + +# flipUVsAfterMirror +Enabling this option will flip the UVs horizontally after mirroring the cards to achieve exact mirroring. + +# enableTooltips +Will toggle the visibility of these tooltips you are reading right now. + +# showLayerCollectionsMenu +Shows layer collections menu widget. + +# importIntoANewCollection +If enabled, all the imported curves will be placed into a new "Imported Curves" layer collection. +If disabled, all the imported curves will be placed into the "Main" layer collection + +# layerNumbersOnly +Layers will use only numbers if enabled. + +# convertToNewLayerSystem +Converts all the layers in the scene to a new layer system that is hidden from the Channel Box Display Layers window. +Layers can still be accessed from Window->Relationship Editors->Display Layer window. + +# updateLayers +Utility function that will manually update all layers. Used for if layers are correct for some reason. + +# resetToDefaults +Resets every option and the GS CurveTools to the default "factory" state. + +# maya2020UVFix +This function will fix any broken UVs when trying to open old scenes in Maya 2020 or 2022 or when opening scenes in 2020 and 2022 when using Maya Binary file type. This will have no effect on older versions of Maya (<2020). This bug is native to Maya and thus can’t be fixed in GS CurveTools plug-in. + +# mayaFixBrokenGraphs +This function will attempt to fix all the broken graphs in the scene. +Use if one of the graphs (Width, Twist or Profile) is in a broken state. + +# convertBezierToNurbs +Converts the selected Bezier curves to NURBS curves. +Bezier curves are not supported by the GS CurveTools. + +# maya2020TwistAttribute +This function will fix any broken cards created in Maya 2020.4 before v1.2.2 update. + +# maya2020UnbindFix +This function will fix any cards that are not unbinding properly and were created before v1.2.3 update in Maya 2020.4. + +# deleteAllAnimationKeys +This function will delete all the animation keys on all the curves present in the scene. +This can fix deformation issues when using duplicate or other GS CurveTools functions. +Some functions (like duplicate) will delete the keys automatically, however the keys can still cause issues. + + + +# warpSwitch +Advanced cards and tubes suitable for longer hair. +Additional options and controls. +Slower than Extrude (viewport performance). +Can have issues on very small scales. + +# extrudeSwitch +Simple cards and tubes suitable for shorter hair and brows. +Has limited controls. +Much faster than Warp (viewport performance). +Better for small scales. + +# newCard +Creates a new Card in the middle of the world. Used at the beginning of the project to create templates. + +# newTube +Creates a new Tube in the middle of the world. Used at the beginning of the project to create templates. + +# curveCard +Converts selected Maya curve to CurveTools Card. + +# curveTube +Converts selected Maya curve to CurveTools Tube. + +# gsBind +Binds selection to a single curve. Creates a Bind Group. Selection options: +1. Single "empty" curve (default Maya curve) and single combined geometry. +2. Single "empty" curve (default Maya curve) and any number of Curve Cards and Curve Tubes. +*** +Shift + Click will duplicate the original curves/geo before binding it to the empty curve. +Same option is available in the Options menu (Duplicate Curves Before Bind). + +# gsUnbind +UnBinds geometry or Cards/Tubes from selected Bound object. +Geometry and Cards/Tubes will be placed at the origin. + +# addCards +Creates Cards in-between selected Cards based on the Add slider value. +Bound objects are NOT supported. +NOTE: Selection order defines the direction of added Cards if more than 2 initial Cards are selected. + +# addTubes +Creates Tubes in-between selected Tubes based on the Add slider value. +Bound objects are NOT supported. +NOTE: Selection order defines the direction of added Tubes if more than 2 initial Tubes are selected. + +# gsFill +Creates Cards/Tubes or Bound Groups in between selected Cards/Tubes or Bound Groups based on the Add slider value. +NOTE 1: Selection order defines the direction of added curves if more than 2 initial curves are selected. +NOTE 2: The type of Card or Tube or Bound Group is defined by the previous curve in the selection chain. + +# gsSubdivide +Subdivides selected curve into multiple curves based on the Add slider value +Shift + Click subdivides selected curve but does not delete the original curve + +# gsEdgeToCurve +Converts selected geometry edges to curves. +Multiple unconnected edge groups can be selected at the same time. + +# gsCardToCurve +Opens the Card-to-Curve UI +Card to curve algorithm will attempt to generate CurveTools cards from selected geometry cards. +Selected geometry cards should be one sided meshes. +Selected geometry cards should be separate objects. + +# layerCollectionsComboBox +Layer collections drop-down menu. +Allows to separate the project into different layer collections, up to 80 layers in each collection. +Has additional functionality in markdown menu (Hold RMB): +*** +Markdown menu: +1. Clear - will delete all the cards from the current layer. Undoable operation. +2. Merge Up, Merge Down - merge all the cards from the current layer to the layer above or below it. +3. Copy, Paste - will copy all the cards from the current layer and paste them to the layer that user selects. +4. Move Up, Move Down - will rearrange the current layer collections by moving the currently selected collection up or down in the list. +5. Rename - will rename the current layer collection + +# layerCollectionsPlus +Add additional layer collection after the current one. + +# layerCollectionsMinus +Remove current layer collection. All the cards from the removed collection will be merged one layer UP. + +# gsAllFilter +Layer filter. Controls the visibility of all objects in all layers: +Normal click will show all curves and geometry in all layers. +Shift + Click will hide all curves and geometry in all layers +Ctrl + Click will show all the curves and geometry in all layers and all collections. +Ctrl + Shift + Click will hide all curves and geometry in all layers and all collections. + +# gsCurveFilter +Layer filter. Hides all geometry and shows all the curves in all layers. +Ctrl + Click will do the same thing, but for all layers and all collections. +NOTE: Holding RMB will open a marking menu with Toggle Always on Top function as well as "Auto-Hide Inactive Curves" function. +Toggle Always on Top function will toggle the Always on Top feature that will show the curve component always on top. The effect is different in different Maya versions +Auto-Hide Inactive Curves will hide all the curve components on all inactive layer collections when switching between collections. + +# gsGeoFilter +Layer filter. Hides all curves and shows only geometry. +Ctrl + Click will do the same thing, but for all layers and all collections. + +# colorMode +Color mode toggle. Enables colors for each layer and (optionally) UV checker material. +NOTE: Checker pattern can be disabled in the Options menu + +# curveGrp0 +Curve Layers that are used for organization of the cards and tubes in the scene. +Selected layer (white outline) will be used to store newly created cards. +Holding RMB will open a marking menu with all the functions of current layer. +*** +Key Combinations: +Shift + Click: additively select the contents of the layers. +Ctrl + Click: exclusively select the contents of the layer. +Alt + Click: show/hide selected layer. +Ctrl + Shift: show/hide curve component on selected layer. +Ctrl + Alt: show/hide geo component for the selected layer. +Shift + Alt + Click: isolate select the layer. +Shift + Ctrl + Alt + Click: enable Always on Top for each layer (only for Maya 2022+). +*** +Layer MMB Dragging: +MMB + Drag: move the contents of one layer to another layer. Combine if target layer has contents. +MMB + Shift + Drag: copy the contents of one layer to another layer. Copy and Add if target layer has contents. + +# gsExtractSelected +Extracts (Duplicates) the geometry component from the selected curves: +*** +Key Combinations: +Normal click will extract geometry and combine it. +Shift + Click will extract geometry as individual cards. +Ctrl + Click will extract geometry, combine it, open export menu and delete the extracted geo after export. +Shift + Ctrl click will extract geometry, open export menu and delete the extracted geo after export. + +# gsExtractAll +Extracts (Duplicates) the geometry component from all layers and collections. Original layers are HIDDEN, NOT deleted: +Last Layer in the current Collection is ignored by default. Can be changed in the options. +Collections with "template" in their name (case insensitive) will be ignored by default. Can be changed in the options. +*** +Key Combinations: +Normal click will extract geometry and combine it. +Shift + Click will extract geometry as individual cards grouped by layers. +Ctrl + Click will extract geometry, combine it, open export menu and delete the extracted geo after export. +Shift + Ctrl click will extract geometry, open export menu and delete the extracted geo after export. + +# gsSelectCurve +Selects the curve components of the selected Curve Cards/Tubes. +NOTE: Useful during the selection in the outliner. + +# gsSelectGeo +Selects the geometry component of the selected Curve Cards/Tubes. +NOTE: Useful for quick assignment of the materials. + +# gsSelectGroup +Selects the group component of the selected Curve Cards/Tubes. +NOTE: Useful when you are deleting curves from viewport selection. + +# gsGroupCurves +Groups the selected curves and assigns the name from Group Name input field (or default name if empty). + +# gsRegroupByLayer +Regroups all the curves based on their layer number, group names and collection names. +Group names can be changed in the Layer Names & Colors menu. +Groups can be colorized if the "Colorize Regrouped Layers" is enabled in the Options menu. +Collections with "template" in their name will be grouped under "CT_Templates". Can be changed in the options. + +# gsGroupNameTextField +The name used by the Group Curves function. +If empty, uses the default name. + +# gsCustomLayerNamesAndColors +Opens a menu where group names and colors can be changed and stored in a global preset. + +# gsTransferAttributes +Transfers attributes from the FIRST selected curve to ALL the other curves in the selection. +NOTE: Shift + Click transfers the attributes from the LAST selected curve to ALL others. +NOTE2: Holding RMB on this button opens a marking menu with Copy-Paste and Filter functionality + +# gsTransferUVs +Transfers UVs from the FIRST selected curve to ALL the other curves in the selection. +NOTE: Shift + Click transfers the UVs from the LAST selected curve to ALL others. +NOTE2: Holding RMB on this button opens a marking menu with Copy-Paste and Filter functionality + +# gsResetPivot +Resets the pivot on all selected curves to the default position (root CV). + +# gsRebuildWithCurrentValue +Rebuild selected curves using current rebuild slider value + +# gsResetRebuildSliderRange +Reset rebuild slider range (1 to 50) + +# gsDuplicateCurve +Duplicates all the selected curves and selects them. +NOTE: You can select either NURBS curve component, geometry component or group to duplicate. + +# gsRandomizeCurve +Opens a window where different attributes of the selected curves can be randomized: +1. Enable the sections of interest and change the parameters. +2. Dragging the sliders in each section enables a PREVIEW of the randomization. Releasing the slider will reset the curves. +3. Click Randomize if you wish to apply the current randomization. + +# gsExtendCurve +Lengthens a selected curves based on the Factor slider. + +# gsReduceCurve +Shortens the selected curves based on the Factor slider. + +# gsSmooth +Smoothes selected curves or curve CVs based on the Factor slider. +NOTE 1: At least 3 CVs should be selected for component smoothing. +NOTE 2: Holding RMB will open a marking menu where you can select a stronger smoothing algorithm. + +# mirrorX +Mirrors or Flips all the selected curves on the World X axis. + +# mirrorY +Mirrors or Flips all the selected curves on the World Y axis. + +# mirrorZ +Mirrors or Flips all the selected curves on the World Z axis. + +# gsControlCurve +Adds a Control Curve Deformer to the selected curves. Can be used to adjust groups of curves. +NOTE 1: Should NOT be used to permanently control clumps of cards. Use Bind instead. + +# gsApplyControlCurve +Applies the Control Curve Deformer. +Either the Control Curve or any controlled Curves can be selected for this to work. + +# gsCurveControlWindow +Opens a Curve Control Window. Contains all the available controls for curves. + +# gsUVEditorMain +Opens a UV editor that can be used to setup and adjust UVs on multiple cards. +NOTE 1: Lambert material with PNG, JPG/JPEG or TIF/TIFF (LZW or No Compression) texture file is recommended. TGA (24bit and no RLE) is also supported. +NOTE 2: Make sure to select the curves or the group, not the geo, to adjust the UVs. +NOTE 3: Using default Maya UV editor will break GS CurveTools Cards, Tubes and Bound Groups. +NOTE 4: Default UV editor can be used when custom geometry is used in a Bound Groups. + + + + +# gsLayerSelector +Shows the Layer of the selected curve. +Selecting different layer will change the layer of all the selected curves. + +# gsColorPicker +Layer/Card Color Picker. + +# gsCurveColorPicker +Curve Color Picker. + +# selectedObjectName +Selected object name. Editing this field will rename all the selected objects. + +# lineWidth +Controls the thickness of the selected curves. + +# gsBindAxisAuto +Automatic selection of the bind Axis (recommended). +NOTE: Change to manual X, Y, Z axis if bind operation result is not acceptable. + +# AxisFlip +Flips the direction of the bound geometry. + +# editOrigObj +Temporarily disables curve bind and shows the original objects. +Used to adjust the objects after the bind. +To add or remove from the Bound group use Unbind. + +# selectOriginalCurves +Selects the original curves that were attached to a bind curve. +Allows to edit their attributes without using Unbind or Edit Original Objects + +# twistCurveFrame +Advanced twist control graph. Allows for precise twisting of the geometry along the curve. Click to expand. + +# Magnitude +Twist multiplier. The larger the values, the more the twist. Default is 0.5. + +# gsTwistGraphResetButton +Resets the graph to the default state. + +# gsTwistGraphPopOut +Opens a larger graph in a separate window that is synced to the main graph. + +# widthLockSwitch +Links/Unlinks the X and Z width sliders. +If linked, the sliders will move as one. + +# LengthLock +Locks/Unlocks the length slider. +When Locked the geometry is stretched to the length of the curve and the slider is ignored. + +# widthCurveFrame +Advanced width control graph. Allows for precise scaling of the geometry along the curve. Click to expand. + +# gsWidthGraphResetButton +Resets the graph to the default state. + +# gsWidthGraphPopOut +Opens a larger graph in a separate window that is synced to the main graph. + +# profileCurveGraph +Advanced control over the profile of the card. Modifies the profile applied by the Profile slider. Click to expand. +Add or remove points and change them to increase or decrease the Profile value along the curve. + +# autoEqualizeSwitchOn +Locks the points horizontally to equal intervals to avoid geometry deformation. + +# autoEqualizeSwitchOff +Unlocks the points and allows for the full control. + +# equalizeCurveButton +Snaps the points to the equal horizontal intervals. + +# gsResetProfileGraphButton +Resets the curve to the default state. + +# gsProfileGraphPopOut +Opens a larger graph in a separate window that is synced to the main graph. + +# reverseNormals +Reverses the normals on the selected cards/tubes. + +# orientToNormalsFrame +Orient selected cards/tubes to the normals of the selected geo. + +# gsOrientToNormalsSelectTarget +Set selected mesh as a target for the algorithm. + +# orientRefreshViewport +Toggles the viewport update during the alignment process. +Disabling can speed up the process. + +# gsOrientToNormals +Starts the alignment process. +Will align the selected cards to the normals of the selected geometry. + +# flipUV +Flips the UVs of the card/tube horizontally. + +# resetControlSliders +Resets the range of the sliders to the default state. + +# UVFrame +Legacy controls for the UVs. Just use the new UV Editor. + +# solidifyFrame +Expands controls that control the thickness of the cards/tubes. + +# solidify +Toggles the thickness of the geometry. + + + +# lengthDivisions +Change the length divisions of the selected cards/tubes. + +# dynamicDivisions +Toggles the dynamic divisions mode. +Dynamic divisions will change the divisions of the cards/tubes based on the length of the curves. +In dynamic divisions mode, L-Div slider will control the density of the divisions, not the fixed divisions count. + +# widthDivisions +Change the width divisions of the selected cards/tubes. + +# Orientation +Change the orientation of the card/tube around the curve. + +# Twist +Smoothly twist the entire geometry card/tube. Twists the tip of the card. + +# invTwist +Smoothly twist the entire geometry card. Twists the root of the card. + +# Width +Change the width of the selected card. + +# Taper +Linearly changes the width of the card/tube along the length of the curve. + +# WidthX +Change the width of the tube along the X axis. + +# WidthZ +Change the width of the tube along the Z axis. + +# Length +Change the length of the attached geometry. Works only when Length Unlock button is checked. + +# Offset +Offset the geometry along the curve. + +# Profile +Change the profile of the card along the length of the curve uniformly. + +# profileSmoothing +Smoothing will smooth the profile transition. + +# otherFrame +Other less used options + +# curveRefine +Controls the number of "virtual" vertices on the curve. These are the vertices that are used to calculate the geometry deformation. +Zero (0) value will disable the refinement and the geometry will be attached directly to the curve. The fastest option. +Larger refine values means smoother geometry that is a closer fit to the curve. +Only increase past 20 if you need additional precision or if there are any visual glitches with the geometry. +Large refine values can cause significant performance drop, lag and other issues on smaller curve sizes. +Recommended values are: +20 for curves with less than 20 CVs. +0 (disabled) or same as the number of CVs for curves with more than 20 CVs. + +# autoRefine +Enables auto-refine for selected curves. Recommended to keep this on. +Manual refinement can be helpful if the geometry deformation is wrong or not precise enough. + +# samplingAccuracy +Increases the sampling accuracy of the deformer that attaches the geometry to a curve. +Larger values = more precise geometry fit to a curve and more lag. + +# curveSmooth +Smoothing of the geometry that is attached to a curve. + +# surfaceNormals +Changes the smoothing angle of the normals of the geometry. + +# gsIterationsSlider +Controls the number of iterations per card. + +# gsMinimumAngle +Controls the target angle difference between the normal of the mesh and the card. + +# solidifyThickness +Controls the amount of thickness on the geometry. + +# solidifyDivisions +Controls the number of divisions on the solidify extrusion. + +# solidifyScaleX +Changes the scale on the X axis. + +# solidifyScaleY +Changes the scale on the Y axis. + +# solidifyOffset +Controls the offset of the solidify extrusion. + +# solidifyNormals +Controls the smoothing angle for normals of the solidify extrusion. + +# geometryHighlight +If enabled, selecting the curve will also highlight the geometry component that is attached to that curve. +Works only on GS CurveTools curves and geo. + +# curveHighlight +If enabled, selected curves and their components will be additinally highlighted for better visibility. +The curves and components will be in X-Ray mode by default. +Colors and transparency values can be changes in the menu below. + +# gsSelectedCVColor +Selected CV highlight color + +# gsSelectedCVAlpha +Selected CV highlight transparency (alpha) + +# gsDeselectedCVColor +Deselected CV highlight color + +# gsDeselectedCVAlpha +Deselected CV highlight transparency (alpha) + +# curveVisibility +Toggle selected curves highlight + +# gsCurveHighlightColor +Selected curve highlight color + +# gsCurveHighlightAlpha +Selected curve highlight transparency (alpha) + +# hullVisibility +Toggle hull visibility. +Hull is a line that connects all the CVs on the curve. + +# gsHullHighlightColor +Hull highlight color + +# gsHullHighlightAlpha +Hull highlight transparency (alpha) + +# advancedVisibilityFrame +Better highlights for selected curves and components. + +# lazyUpdate +Enables lazy update for selected curves. +Lazy update can slightly increase the performance of the highlight, +however it has some visual drawbacks (curve highlight can fail to update when switching curves in component selection mode) + +# alwaysOnTop +Toggles X-Ray (always on top) drawing for highlighted components. +Disabling this defeats the purpose of the advanced visibility, but hey, it's your choice. + +# curveDistanceColor +Toggles the distance color effect on the curve highlight. +Distance color darkens the curve color the further it is from the camera. + +# cvDistanceColor +Toggles the distance color effect on the CV highlight. +Distance color darkens the CVs color the further it is from the camera. + +# hullDistanceColor +Toggles the distance color effect on the hull highlight. +Distance color darkens the hull color the further it is from the camera. + +# gsDistanceColorMinValue +Distance color minimum. +This value is the minimum allowed color multiplier for the Distance Color effect. +The lower this value, the darker further parts of the curve will be. +Black at 0.0 +Original color at 1.0 + +# gsDistanceColorMaxValue +Distance color maximum. +This value is the maximum allowed color multiplier for the Distance Color effect. +The higher this value, the brighter closest parts of the curve will be. +Black at 0.0 +Original color at 1.0 + +# CVocclusion +Toggles the experimental CV occlusion mode (hull is affected as well) +When the appropriate mesh name is added to Occluder Mesh input field, +this function will automatically hide CVs and hull lines that are behind this mesh (even in X-Ray mode). +Warning: enabling this mode can negatively impart viewport performance. + +# gsSelectOccluderButton +This button adds the selected mesh name to the Occluder Mesh input field. + +# gsOccluderMeshName +Type the full path for the occluder mesh here, or use the "Select Occluder" button on the left <- + + + + +# gsGenerateLayerColorGradient +Generate a color gradient for the layer colors. +Rows control the number of Rows to generate. +Left color picker sets the initial color. +Right color picker sets the final color. + +# gsRandomizeLayerColors +Generate random colors for the layers. +SatMin controls the minimum allowed saturation. +SatMax controls the maximum allowed saturation. + +# gsResetAllLayerColors +Resets all the color swatches to the default color. + +# gsGetCurrentSceneLayers +Populates the menu with the names and colors stored in the scene. + +# gsSetAsCurrentSceneLayers +Applies the names and colors from the menu to the scene. + +# gsLoadGlobalLayerPreset +Load the global names and colors preset to the menu. +NOTE: Don't forget to Set to Scene before closing the menu. + +# gsSaveGlobalLayerPreset +Saves the current names and colors from the menu to the global preset. + + + +# gsUVSelect +Enables the selection of the UVs. +Drag to draw a box selection. + +# gsUVMove +Enables the Move tool. +Move the selected UVs or move individual UVs if nothing is selected. + +# gsUVRotate +Enables the Rotation of the selected UVs. +Hold LMB and drag anywhere in the viewport to rotate the selected UVs. +Rotation pivot is the center of the individual unscaled UV. + +# gsUVScale +Enables the Scaling of the selected UVs. +Hold LMB and drag in the viewport to scale the card Horizontally of Vertically. +Repeated hotkey click will toggle between Horizontal and Vertical scaling. + +# gsUVHorizontalScale +Horizontal scaling mode selector. + +# gsUVVerticalScale +Vertical scaling mode selector. + +# gsDrawUVs +Enables the UVs Drawing Tool: +1. Select UVs using Selection Mode. +2. Enable the UV Drawing Tool. +3. Draw a UV Rectangle anywhere in the viewport to create/move the UVs there. + +# gsHorizontalFlipUV +Flips the selected UVs horizontally. +Flipped UVs have the blue circle indicator inside the root rectangle. + +# gsVerticalFlipUV +Flips the selected UVs vertically. + +# gsResetUVs +Resets the selected UVs to the default 0,1 rectangle. + +# gsSyncSelectionUVs +Syncs selection between UV editor and Maya Viewport. + +# gsRandomizeUVs +Randomize selected UV positions between already existing UV positions. +*** +Normal click will keep the overall density distribution of the UVs. +This means that if there is one card in one position and twenty cards in the other, +it will keep this distribution of 1 to 20. +*** +Shift+Click will ignore the original density distribution +and simply randomize the UVs between the original positions. + +# gsFocusUVs +Focuses on the selected UVs or on all the UVs if nothing is selected. + +# gsUVIsolateSelect +Hides all the unselected UVs and shows only the selected ones. + +# gsUVShowAll +Shows all the hidden UVs. + +# UVEditorUseTransforms +Use "Coverage" and "Translate Frame" parameters from place2dTexture node for texture. +Offset is not supported. +Diffuse and Alpha channel MUST have the same coverage and translate frame values. + +# UVEditorTransparencyToggle +Enable texture map transparency using Alpha map from Transparency plug in the material node + +# UVEditorBGColorPicker +Background Color + +# UVEditorGridColorPicker +Grid Color + +# UVEditorFrameColorPicker +Frame Color + +# UVEditorUVFrameSelectedColorPicker +Selected UV frame color + +# UVEditorUVFrameDeselectedColorPicker +Deselected UV frame color + +# UVEditorUVCardFillColorPicker +UV frame background color + + + +# gsCardToCurve_outputTypeSwitch +Controls the output of Card-to-Curve algorithm + +# gsCardToCurve_generateCards +Generate cards from selected one-sided geometry + +# gsCardToCurve_generateCurves +Generate curves from selected one-sided geometry + +# gsCardToCurve_cardType +Controls the type of generated cards + +# gsCardToCurve_warp +Generate Warp cards + +# gsCardToCurve_extrude +Generate Extrude cards + +# gsCardToCurve_matchAttributes +Controls which attributes on the new cards should be approximated from the original geometry. +NOTE: This process is not perfect and final result can be inaccurate. + +# gsCardToCurve_orientation +Match orientation attribute during the generation process + +# gsCardToCurve_width +Match orientation attribute during the generation process + +# gsCardToCurve_taper +Match taper attribute during the generation process + +# gsCardToCurve_twist +Match twist attribute during the generation process + +# gsCardToCurve_profile +Match profile attribute during the generation process + +# gsCardToCurve_material +Copy material (shader) from the original geometry + +# gsCardToCurve_UVs +Tries to approximate the UVs from the original geometry +NOTE: Matches the bounding box of the UVs. Rotated and deformed UVs are not matched precisely. + +# gsCardToCurve_UVMatchOptions +Controls UV matching behaviour + +# gsCardToCurve_verticalFlip +Vertically flip matched UVs + +# gsCardToCurve_horizontalFlip +Horizontally flip matched UVs + +# gsCardToCurve_reverseCurve +Reverse generated curve direction +Root CV should be generated near the scalp of the model. +Enable or disable if resulting card direction and taper are reversed. + +# gsCardToCurve_convertSelected +Convert selected geometry to cards or curves based on the options diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/tooltips.py b/Scripts/Modeling/Edit/gs_curvetools/utils/tooltips.py new file mode 100644 index 0000000..4ef0eaf --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/tooltips.py @@ -0,0 +1,102 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" +import io +import os + +gs_curvetools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace('\\', '/') + +def processTooltips(): + # type: () -> dict + """ Processes the tooltips markdown file and returns a dict {"name": "tooltip", ...} """ + from . import utils + filePath = os.path.join(gs_curvetools_path, 'utils', 'tooltips.md').replace('\\', '/') + finalDict = {} + with io.open(filePath, "r", encoding="utf-8") as f: + lines = f.readlines() + title = "" + tooltip = "" + commentBlock = False + for line in lines: + line = line.strip() + if not line or "": + continue + + # Exclude comments + if "" in line: + continue + if "" in line and commentBlock: + commentBlock = False + continue + if commentBlock: + continue + + # Process tooltips + if '#' in line: + if title and tooltip: + finalDict[title] = tooltip.strip() + title = line.replace("#", "").strip() + tooltip = "" + else: + tooltip += line + "\n" + # Final title/tooltip pair + if title and tooltip: + finalDict[title] = tooltip.strip() + return finalDict + + +def toggleCustomTooltipsMain(enable=True): + # Custom Tooltips that can't be automatically processed from markdown files (mostly Maya sliders) + from . import wrap + customTooltips = { + 'gsCurvesSlider': 'Selects the number of added curves.\nUsed by Add Card, Add Tube, Fill and Subdivide functions', + 'gsSelectCVSlider': 'Selects the CVs of the selected curves based on the position of the slider.\n<- Left is root of the curve and right is the tip ->\nModes:\nNormal Drag is single CV selection based on the position of the slider.\nShift+Drag is additive selection.\nAlt+Drag is subtractive selection.\nCtrl+Drag to move the slider without selection change.', + 'gsRebuildSlider': 'Interactively rebuilds the selected curves. Slider controls the target number of CVs.\nNOTE: very small scale curves can have issues with distortion. If it is the case, try using Maya curve rebuild command.', + 'gsFactorSlider': 'Adjusts the intensity of the Smooth slider and the power of Extend and Reduce functions', + } + + for widget in customTooltips: + if hasattr(wrap.WIDGETS[widget], "setToolTip") and callable(getattr(wrap.WIDGETS[widget], "setToolTip")): + if enable: + wrap.WIDGETS[widget].setToolTip(customTooltips[widget]) + else: + wrap.WIDGETS[widget].setToolTip('') + +def toggleCustomTooltipsCurveControl(enable=True): + # Custom Tooltips that can't be automatically processed from markdown files (mostly Maya sliders) for Curve Control Window + from . import wrap + customTooltips = { + 'gsPointSizeSlider': 'Controls the size of a CV point highlight', + 'gsCurveWidthSlider': 'Controls the width of the curve highlight', + 'gsHullWidthSlider': 'Controls the width of the hull highlight', + } + + for widget in customTooltips: + if hasattr(wrap.WIDGETS[widget], "setToolTip") and callable(getattr(wrap.WIDGETS[widget], "setToolTip")): + if enable: + wrap.WIDGETS[widget].setToolTip(customTooltips[widget]) + else: + wrap.WIDGETS[widget].setToolTip('') diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/utils.py b/Scripts/Modeling/Edit/gs_curvetools/utils/utils.py new file mode 100644 index 0000000..1831c45 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/utils.py @@ -0,0 +1,1426 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +import cProfile +import functools +import inspect +import itertools +import logging +import math +import os +import pstats +import re +import subprocess +import sys +import traceback + +try: + from collections.abc import Iterable +except BaseException: + from collections import Iterable + +from functools import partial as pa +from functools import wraps +from imp import reload +from os import path + +import maya.api.OpenMaya as om +import maya.cmds as mc +import maya.mel as mel + +from ..constants import * + +gs_curvetools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace('\\', '/') + +### Logging ### +class Logger: + """ Create logger, log and print messages""" + + def __init__(self): + logFilePath = os.path.join(gs_curvetools_path, 'log.log').replace('\\', '/') # type: str + # Check if file already there and its size. Delete if too large. + if os.path.exists(logFilePath): + size = os.stat(logFilePath).st_size + if size >= 1024 * 1024: # 1 mb max log size + try: + os.remove(logFilePath) + except BaseException: + pass + # Create logger + self.logger = logging.getLogger("GS_CurveTools") + self.logger.setLevel(logging.DEBUG) + self.logger.propagate = False + # File handler + fileLog = logging.FileHandler(filename=logFilePath, encoding='utf-8') + fileLog.set_name("File Output") + fileLog.setLevel(logging.DEBUG) + # Console handler + consoleLog = logging.StreamHandler(stream=sys.stdout) + consoleLog.set_name("Console Output") + consoleLog.setLevel(logging.INFO) + # Formatter + fileFormatter = logging.Formatter( + fmt='[%(asctime)s,%(msecs)03d | %(levelname)5.5s | %(module)8.8s | %(lineno)5d]: %(funcName)s() : %(message)s', + datefmt='%H:%M:%S') + consoleFormatter = logging.Formatter( + fmt='[GS CurveTools|%(levelname)s]: %(message)s') + fileLog.setFormatter(fileFormatter) + consoleLog.setFormatter(consoleFormatter) + # Add handlers to the logger + self.logger.handlers = [] + self.logger.addHandler(fileLog) + self.logger.addHandler(consoleLog) + + def inView(self, message): + """ InView Message """ + self.logger.debug(message) + mc.inViewMessage(smg=str(message), pos='topCenter', f=1, fit=50, fot=50) + + def warning(self, message): + """ Print Warning Message """ + self.logger.error(message) + trace = traceback.format_stack(limit=2) + self.logger.debug(trace[0].splitlines()[0].strip()) + mc.warning(message) + + def warningInView(self, message): + """ Print Warning and InView message """ + self.logger.error(message) + trace = traceback.format_stack(limit=2) + self.logger.debug(trace[0].splitlines()[0].strip()) + mc.warning(message) + mc.inViewMessage(smg=message, pos='topCenter', f=1, fit=50, fot=50) + + def printInView(self, message): + """ Print to Script Editor and InView message """ + self.logger.info(message) + mc.inViewMessage(smg=message, pos='topCenter', f=1, fit=50, fot=50) + + def openLogFile(self): + if OS == "mac": + self.warning('Opening log file is not supported on Mac. Please open it manually from gs_curvetools folder:') + self.warning('{}'.format(path.join(gs_curvetools_path, 'log.log').replace('\\', '/'))) + return + subprocess.call('notepad "{}"'.format(path.join(gs_curvetools_path, 'log.log').replace('\\', '/')), creationflags=0x00000008) + + +logger = Logger() + +MESSAGE = logger +LOGGER = logger.logger + + +# Some version specific compatibility stuff +if sys.version_info >= (3, 0): + from io import StringIO + fixedWraps = wraps +else: + # Old python import for StringIO + from io import BytesIO as StringIO + + # Legacy wraps for python 2.7 to fix some errors + def wrapsSafely(obj, attr_names=functools.WRAPPER_ASSIGNMENTS): + return wraps(obj, assigned=itertools.ifilter(functools.partial(hasattr, obj), attr_names)) + fixedWraps = wrapsSafely + + +### Decorators ### + +def deferred(func): + """Deferred execution""" + @fixedWraps(func) + def wrapper(*args, **kwargs): + try: + mc.evalDeferred(pa(func, *args, **kwargs)) + except Exception as e: + LOGGER.exception(e) + return wrapper + + +def deferredLp(func): + """Deferred execution (lowest priority)""" + @fixedWraps(func) + def wrapper(*args, **kwargs): + try: + rv = mc.evalDeferred(pa(func, *args, **kwargs), lp=1) + except Exception as e: + LOGGER.exception(e) + return rv + return wrapper + + +def executeNext(func): + @fixedWraps(func) + def wrapper(*args, **kwargs): + try: + mc.evalDeferred(pa(func, *args, **kwargs), en=1) + except Exception as e: + LOGGER.exception(e) + return wrapper + + +def parseMelCommand(pythonCmd, *args, **_): + """ Convert python function call to MEL compatible call with all the args""" + if inspect.ismethod(pythonCmd): + instName = pythonCmd.__self__.name + else: + instName = False + module = pythonCmd.__module__ + function = pythonCmd.__name__ + arguments = "({})".format(','.join(str(x) if not isinstance(x, str) else "'{}'".format(str(x)) for x in args)) + if instName: + cmd = "{}.{}.{}{}".format(module, instName, function, arguments) + else: + cmd = "{}.{}{}".format(module, function, arguments) + melCommand = 'python("import {};{}");'.format(module, cmd) + return melCommand + + +def undo(func): + """ Safe Undo Open/Close Chunk """ + @fixedWraps(func) + def wrapper(*args, **kwargs): + funcName = str(func.__name__) + mc.undoInfo(ock=1, cn=funcName) + rv = None + try: + rv = func(*args, **kwargs) + try: + mc.repeatLast( + ac=parseMelCommand(func, *args, **kwargs), + acl=funcName + ) + except Exception as e: + LOGGER.exception(e) + except Exception as e: + LOGGER.exception(e) + finally: + mc.undoInfo(cck=1) + return rv + return wrapper + + +def noUndo(func): + """ Safe "State Without Flush" """ + @fixedWraps(func) + def wrapper(*args, **kwargs): + mc.undoInfo(swf=0) + rv = None + try: + rv = func(*args, **kwargs) + except Exception as e: + LOGGER.exception(e) + finally: + mc.undoInfo(swf=1) + return rv + return wrapper + + +### Time classes ### + + +class Timer: + """ Timer """ + t0 = 0 + + @classmethod + def increment(cls, sec): + cls.t = mc.timerX() + if cls.t >= cls.t0 + sec: + cls.t0 = cls.t + return True + else: + return False + + +### Debug ### + + +class Profiler(): + + def __init__(self): + self.pr = cProfile.Profile() + self.pr.enable() + + def end(self): + self.pr.disable() + s = StringIO() + sort = 'tottime' + ps = pstats.Stats(self.pr, stream=s).sort_stats(sort) + ps.print_stats() + print(s.getvalue()) # pylint: disable=print-statement + + +def profile(func): + """Profiles the execution speed of the function or method""" + @fixedWraps(func) + def wrapper(*args, **kwargs): + p = Profiler() + rv = func(*args, **kwargs) + p.end() + return rv + return wrapper + + +class DebugDraw: + + timer = Timer() + + @staticmethod + def returnMaterial(color): + matName = 'GS_DEBUG_Material_{}_{}_{}'.format( + str(color[0]).replace('.', ''), + str(color[1]).replace('.', ''), + str(color[2]).replace('.', '') + ) + if not mc.objExists(matName): + lambert = mc.shadingNode('lambert', asShader=1, n=matName, ss=1) + mc.setAttr(lambert + '.color', color[0], color[1], color[2]) + mc.setAttr(lambert + '.incandescence', color[0], color[1], color[2]) + mc.setAttr(lambert + '.ambientColor', color[0], color[1], color[2]) + mc.setAttr(lambert + '.transparency', 0.5, 0.5, 0.5) + lambert_set = mc.sets(r=1, nss=1, em=1, n=matName + '_set') + mc.connectAttr(lambert + '.outColor', lambert_set + '.surfaceShader') + return lambert_set + else: + lambert_set = mc.listConnections(matName + '.outColor', t='shadingEngine')[0] + return lambert_set + + @classmethod + def sceneCleanup(cls, ignoreTimer=False): + # Don't cleanup the scene if multiple objects are created at the same time + if not cls.timer.increment(5) and not ignoreTimer: + return + allNodes = mc.ls() + for node in allNodes: + if 'GS_DEBUG_' in node and mc.objExists(node) and mc.nodeType(node) == 'transform': + mc.delete(node) + + @classmethod + def setGeoStyle(cls, geo, color): + # type: (list[tuple[str, str]], tuple[float, float, float]) -> None + """ Apply debug render style to list of transforms """ + if not isinstance(geo, list) and not isinstance(geo, tuple): + geo = [geo] + for tr in geo: + mc.setAttr(tr + '.overrideEnabled', 1) + mc.setAttr(tr + '.overrideDisplayType', 2) + mc.setAttr(tr + '.overrideRGBColors', 1) + mc.setAttr(tr + '.overrideColorA', 0) + mat = cls.returnMaterial(color) + mc.sets(tr, e=1, fe=mat, nw=1) + + @classmethod + def sphere(cls, center, radius=0.1, dir=(0, 1, 0), color=(0, 1, 0), label=''): + cls.sceneCleanup() + center = (center[0], center[1], center[2]) + sphere = mc.polySphere(n="GS_DEBUG_Sphere#", ax=dir, r=radius, sx=8, sy=8, ch=0, o=1, cuv=0) + mc.move(center[0], center[1], center[2], sphere, xyz=1) + cls.setGeoStyle(sphere, color) + if label: + mc.headsUpMessage(label, o=sphere[0], time=100, vp=1) + + @classmethod + def arrow(cls, origin=(0, 0, 0), target=(1, 0, 0), size=0.1, color=(0, 1, 0), label=''): + cls.sceneCleanup() + origSel = mc.ls(sl=1) + origin = (origin[0], origin[1], origin[2]) + target = (target[0], target[1], target[2]) + cylinderTransform, cylinderCreator = mc.polyCylinder(n='GS_DEBUG_Arrow#', ch=1, ax=(0, 1, 0), r=0.1, h=1, sh=1, sc=1, sa=8) + pyramidTransform, pyramidCreator = mc.polyPyramid(ch=1, ax=(0, 1, 0), w=1, ns=4) + + mc.setAttr(pyramidTransform + '.inheritsTransform', 0) + multX = mc.createNode('multDoubleLinear') + mc.setAttr(multX + '.input2', 0.3) + mc.connectAttr(cylinderTransform + '.scaleX', multX + '.input1', f=1) + mc.connectAttr(multX + '.output', pyramidTransform + '.scaleX') + multZ = mc.createNode('multDoubleLinear') + mc.setAttr(multZ + '.input2', 0.3) + mc.connectAttr(cylinderTransform + '.scaleZ', multZ + '.input1', f=1) + mc.connectAttr(multZ + '.output', pyramidTransform + '.scaleZ') + add = mc.createNode('addDoubleLinear') + mc.connectAttr(cylinderTransform + '.scaleX', add + '.input1') + mc.connectAttr(cylinderTransform + '.scaleZ', add + '.input2') + divide = mc.createNode('multDoubleLinear') + mc.setAttr(divide + '.input2', .5) + mc.connectAttr(add + '.output', divide + '.input1') + mc.connectAttr(divide + '.output', pyramidTransform + '.scaleY') + mc.setAttr(pyramidCreator + '.heightBaseline', -1) + mc.setAttr(cylinderCreator + '.heightBaseline', -1) + mc.select(cylinderTransform + '.vtx[17]', pyramidTransform, r=1) + mc.pointOnPolyConstraint(w=1) + mc.parent(pyramidTransform, cylinderTransform) + mc.move(origin[0], origin[1], origin[2], cylinderTransform, xyz=1) + locator = mc.spaceLocator(p=(0, 0, 0)) + mc.xform(locator, cp=1, t=target, a=1) + aim = mc.aimConstraint(locator, cylinderTransform, aim=(0, 1, 0)) + dist = mc.createNode('distanceBetween') + mc.connectAttr(locator[0] + '.translate', dist + '.point1') + mc.connectAttr(cylinderTransform + '.translate', dist + '.point2') + mult = mc.createNode('multDoubleLinear') + mc.connectAttr(divide + '.output', mult + '.input1') + mc.setAttr(mult + '.input2', -0.7) + subtract = mc.createNode('addDoubleLinear') + mc.connectAttr(mult + '.output', subtract + '.input2') + mc.connectAttr(dist + '.distance', subtract + '.input1') + mc.connectAttr(subtract + '.output', cylinderTransform + '.scaleY') + mc.setAttr(cylinderTransform + ".sx", size) + mc.setAttr(cylinderTransform + ".sz", size) + cls.setGeoStyle([pyramidTransform, cylinderTransform], color) + mc.delete(aim, locator) + mc.select(cylinderTransform, r=1) + mc.select(origSel, r=1) + if label: + mc.headsUpMessage(label, o=cylinderTransform, time=100, vp=1) + + +### Misc utils ### + + +def stopUI(state=False): + from .. import core + windows = [ + core.MAIN_WINDOW_NAME, + core.CURVE_CONTROL_NAME, + core.UV_EDITOR_NAME, + core.SCALE_FACTOR_UI, + 'GSCT_RandomizeCurvePopOut', + 'GSCT_AttributesFilterPopOut', + 'GSCT_TwistGraphPopOut', + 'GSCT_WidthGraphPopOut', + 'GSCT_CurveThicknessWindow', + 'GSCT_LayerEditorWindow', + 'GSCT_CustomLayerColorsWindow', + 'GSCT_OrientToNormalsWindow', + 'GSCT_CardToCurvePopOut', + ] + + # Delete advanced visibility node + if all(mc.getClassification("GSCT_CurveTools_DrawManagerNode")): + sel = mc.ls(typ="GSCT_CurveTools_DrawManagerNode") + for n in sel: + if "GSCT_CurveTools_DrawManager" in n: + mc.delete(mc.listRelatives(n, p=1, pa=1)) + + # Delete workspaces + for window in windows: + if MAYA_VER <= 2017: + if mc.workspaceControl(window, q=1, ex=1): + mc.workspaceControl(window, e=1, floating=1) + mc.deleteUI(window) + return 0 + + if mc.workspaceControl(window, q=1, ex=1): + if mc.workspaceControl(window, q=1, vis=1): + if mc.workspaceControl(window, q=1, fl=1): + mc.workspaceControl(window, e=1, clp=False) + mc.workspaceControl(window, e=1, cl=1) + elif mc.workspaceControl(window, q=1, clp=1): + mc.workspaceControl(window, e=1, clp=False) + mc.workspaceControl(window, e=1, cl=1) + else: + mc.workspaceControl(window, e=1, cl=1) + mc.deleteUI(window) + if state: + if mc.workspaceControlState(window, q=1, ex=1): + LOGGER.info("Deleting window state for %s" % window) + mc.workspaceControlState(window, r=1) + + +@deferred +def resetUI(): + resetOptionVars() + if MAYA_VER >= 2018: + stopUI(True) + + # Reload all files + from importlib import import_module + files = [ + '.constants', + '.core', + '.main', + '.ui', + '.uv_editor', + '.utils.gs_math', + '.utils.style', + '.utils.tooltips', + '.utils.utils', + '.utils.wrap', + ] + modules = {} + for file in files: + modules[file] = import_module(file, 'gs_curvetools') + for module in modules: + reload(modules[module]) + + # Run main function + if '.main' in modules: + modules['.main'].main() + else: + try: + from .. import main + main.main() + except ImportError as e: + LOGGER.exception(e) + raise ImportError('Main not found!') + + +def resetOptionVars(): + mc.optionVar(fv=["GSCT_globalScaleFactor", 1]) + mc.optionVar(fv=["GSCT_globalCurveThickness", -1]) + mc.optionVar(iv=['GSCT_warpSwitch', 1]) + mc.optionVar(iv=["GSCT_syncCurveColor", 0]) + mc.optionVar(iv=['GSCT_colorOnlyDiffuse', 1]) + mc.optionVar(iv=["GSCT_colorizedRegroup", 0]) + mc.optionVar(iv=["GSCT_checkerPattern", 0]) + mc.optionVar(iv=["GSCT_ignoreLastLayer", 1]) + mc.optionVar(iv=["GSCT_ignoreTemplateCollections", 1]) + mc.optionVar(iv=["GSCT_groupTemplateCollections", 1]) + mc.optionVar(iv=["GSCT_syncOutlinerLayerVis", 1]) + mc.optionVar(iv=["GSCT_keepCurveAttributes", 1]) + mc.optionVar(iv=["GSCT_massBindOption", 0]) + mc.optionVar(iv=["GSCT_boundCurvesFollowParent", 1]) + mc.optionVar(iv=["GSCT_bindDuplicatesCurves", 0]) + mc.optionVar(iv=["GSCT_bindFlipUVs", 1]) + mc.optionVar(iv=["GSCT_populateBlendAttributes", 1]) + mc.optionVar(iv=["GSCT_convertInstances", 1]) + mc.optionVar(iv=["GSCT_replacingCurveLayerSelection", 1]) + mc.optionVar(iv=["GSCT_flipUVsAfterMirror", 1]) + mc.optionVar(iv=["GSCT_useAutoRefineOnNewCurves", 1]) + mc.optionVar(iv=["GSCT_layerNumbersOnly", 0]) + mc.optionVar(iv=['GSCT_2layerRows', 1]) + mc.optionVar(iv=['GSCT_3layerRows', 0]) + mc.optionVar(iv=['GSCT_4layerRows', 0]) + mc.optionVar(iv=['GSCT_6layerRows', 0]) + mc.optionVar(iv=['GSCT_8layerRows', 0]) + mc.optionVar(iv=['GSCT_UVBugMessageDismissed', 0]) + mc.optionVar(iv=['GSCT_UVEditorTransparencyToggle', 0]) + mc.optionVar(iv=['GSCT_UVEditorAlphaOnly', 0]) + mc.optionVar(iv=['GSCT_enableTooltips', 1]) + mc.optionVar(iv=['GSCT_showLayerCollectionsMenu', 1]) + mc.optionVar(iv=['GSCT_importIntoANewCollection', 0]) + mc.optionVar(iv=['GSCT_AutoHideCurvesOnInactiveCollections', 0]) + mc.optionVar(sv=['GSCT_UVEditorBGColor', "(36, 36, 36)"]) + mc.optionVar(sv=['GSCT_UVEditorGridColor', "(50, 50, 50)"]) + mc.optionVar(sv=['GSCT_UVEditorFrameColor', "(160, 75, 75)"]) + mc.optionVar(sv=['GSCT_UVEditorUVFrameSelectedColor', "(255, 255, 255)"]) + mc.optionVar(sv=['GSCT_UVEditorUVFrameDeselectedColor', "(128, 128, 128)"]) + mc.optionVar(sv=['GSCT_UVEditorUVCardFillColor', "(96, 100, 160)"]) + mc.optionVar(sv=['GSCT_AttributesFilter', "{'Orientation': False}"]) + mc.optionVar(iv=['GSCT_CardToCurveOutputType', 0]) + mc.optionVar(iv=['GSCT_CardToCurveCardType', 0]) + mc.optionVar(iv=['GSCT_GeometryHighlightEnabled', 0]) + mc.optionVar( + sv=['GSCT_CardToCurveOptions', + "{'gsCardToCurve_horizontalFlip': False, 'gsCardToCurve_verticalFlip': False}"] + ) + # Advanced visibility options + mc.optionVar(fv=['GSCT_' + 'gsPointSizeSlider', 10.5]) + mc.optionVar(fv=['GSCT_' + 'gsCurveWidthSlider', 4.0]) + mc.optionVar(fv=['GSCT_' + 'gsHullWidthSlider', 3.0]) + mc.optionVar(sv=['GSCT_' + 'gsDeselectedCVColor', "[1, 0, 0]"]) + mc.optionVar(fv=['GSCT_' + 'gsDeselectedCVAlpha', 1.0]) + mc.optionVar(sv=['GSCT_' + 'gsSelectedCVColor', "[0, 1, 0]"]) + mc.optionVar(fv=['GSCT_' + 'gsSelectedCVAlpha', 1.0]) + mc.optionVar(sv=['GSCT_' + 'gsCurveHighlightColor', "[0, 0, 1]"]) + mc.optionVar(fv=['GSCT_' + 'gsCurveHighlightAlpha', 1.0]) + mc.optionVar(sv=['GSCT_' + 'gsHullHighlightColor', "[0.5, 0, 0.5]"]) + mc.optionVar(fv=['GSCT_' + 'gsHullHighlightAlpha', 1.0]) + mc.optionVar(iv=['GSCT_' + 'gsCurveVisibilityToggle', 1]) + mc.optionVar(iv=['GSCT_' + 'gsHullVisibilityToggle', 0]) + + mc.optionVar(iv=['GSCT_' + 'gsLazyUpdateToggle', 0]) + mc.optionVar(iv=['GSCT_' + 'gsAlwaysOnTopToggle', 1]) + + mc.optionVar(iv=['GSCT_' + 'gsCVDistanceColor', 1]) + mc.optionVar(iv=['GSCT_' + 'gsHullDistanceColor', 1]) + mc.optionVar(iv=['GSCT_' + 'gsCurveDistanceColor', 1]) + mc.optionVar(fv=['GSCT_' + 'gsDistanceColorMinValue', 0.25]) + mc.optionVar(fv=['GSCT_' + 'gsDistanceColorMaxValue', 1.0]) + + mc.optionVar(iv=['GSCT_' + 'gsEnableCVOcclusion', 0]) + mc.optionVar(sv=['GSCT_' + 'gsOccluderMeshName', ""]) + + +def fixMaya2020UVs(): + success = 0 + total = 0 + for i in range(80): + try: + group = 'curveGrp_%s_Geo' % i + if mc.objExists(group): + geometry = mc.editDisplayLayerMembers(group, q=1, fn=1, nr=1) + for geo in geometry: + total += 1 + allUVNodes = list() + history = mc.listHistory(geo, il=0) + for node in history: + if mc.nodeType(node) == 'polyMoveUV': + allUVNodes.append(node) + for node in allUVNodes: + mc.setAttr(node + '.inputComponents', 1, 'map[*]', type='componentList') + success += 1 + except BaseException: + pass + additionalMessage = '' + if success != total: + additionalMessage = ' Some cards were not fixed!' + MESSAGE.printInView('Fixed %s/%s Cards.%s' % (success, total, additionalMessage)) + + +def fixMaya2020Twist(): + dialog = mc.confirmDialog( + title='Fix Maya 2020.4 Twist', + message='This command will fix Maya 2020.4 Twist Attribute Bug\n\ +Only use it if you have issues with Twist and Inv.Twist Attributes\n\n\ +Proceed?', + button=['Yes', 'No'], + defaultButton='Yes', + cancelButton='No', + dismissString='No', + icon='information' + ) + if dialog == 'No': + return + success = 0 + total = 0 + for i in range(80): + try: + group = 'curveGrp_%s_Curve' % i + if mc.objExists(group): + curve = mc.editDisplayLayerMembers(group, q=1, fn=1, nr=1) + for crv in curve: + if mc.attributeQuery('invTwist', n=crv, ex=1): + total += 1 + twist = mc.listConnections(crv + '.invTwist', d=1, scn=1) + twistHandle = mc.listConnections(twist[0] + '.deformerData', s=1, scn=1) + twistHandleShape = mc.listRelatives(twistHandle[0], c=1, pa=1) + connectionCheck = mc.isConnected(twist[0] + '.startAngle', twistHandleShape[0] + '.startAngle') + if not connectionCheck: + mc.connectAttr(twist[0] + '.startAngle', twistHandleShape[0] + '.startAngle', f=1) + mc.connectAttr(twist[0] + '.endAngle', twistHandleShape[0] + '.endAngle', f=1) + success += 1 + except BaseException: + pass + MESSAGE.printInView('Fixed %s/%s Cards.' % (success, total)) + + +def fixMaya2020Unbind(): + dialog = mc.confirmDialog( + title='Fix Maya 2020.4 Unbind', + message='This command will fix Maya 2020.4 Unbind Function Bug\n\ +Only use it if you have issues with Unbind Function\n\n\ +Proceed?', + button=['Yes', 'No'], + defaultButton='Yes', + cancelButton='No', + dismissString='No', + icon='information' + ) + if dialog == 'No': + return + + success = 0 + total = 0 + for i in range(80): + try: + group = 'curveGrp_%s_Curve' % i + if mc.objExists(group): + curve = mc.editDisplayLayerMembers(group, q=1, fn=1, nr=1) + for crv in curve: + if mc.attributeQuery('profileMagnitude', n=crv, ex=1): + total += 1 + ffd = mc.listConnections(crv + '.profileMagnitude', s=0, d=1) + origGeo = mc.listConnections(ffd[0] + '.originalGeometry[0]', s=1, d=0, p=1) + if origGeo: + mc.disconnectAttr(origGeo[0], ffd[0] + '.originalGeometry[0]') + success += 1 + except BaseException: + pass + MESSAGE.printInView('Fixed %s/%s Cards.' % (success, total)) + + +class GetFolder: + """ Get various folders from Maya """ + + def scripts(self): + return mc.internalVar(usd=1) + + def root(self): + return path.join(gs_curvetools_path, '') + + def fonts(self): + return path.join(gs_curvetools_path, 'fonts', '') + + def icons(self): + return path.join(gs_curvetools_path, 'icons', '') + + def plugins(self): + return path.join(gs_curvetools_path, 'plugins', '') + + +getFolder = GetFolder() + + +def attrExists(obj, attr): + """ Check if attribute exists """ + if mc.objExists(obj): + if mc.attributeQuery(attr, n=obj, ex=1): + return 1 + else: + return 0 + else: + return 0 + + +def getAttr(obj, attr): + """ Check if obj exist, check if attr exist, return attr """ + if mc.objExists(obj): + if mc.attributeQuery(attr, n=obj, ex=1): + return mc.getAttr(obj + '.' + attr) + else: + return None + else: + return None + + +class ProgressBar(): + """Progress bar for Maya UI""" + + def __init__(self, name, maxValue): + self.mainProgressBar = mel.eval('$tmp = $gMainProgressBar') + self.end() + mc.progressBar(self.mainProgressBar, edit=True, beginProgress=True, isInterruptable=True, + status=name, minValue=0, maxValue=maxValue) + + def tick(self, step): + """ Returns "True" if ESC is pressed """ + if mc.progressBar(self.mainProgressBar, query=True, isCancelled=True): + self.end() + MESSAGE.warning('Function is Cancelled!') + return True + mc.progressBar(self.mainProgressBar, edit=True, step=step) + return False + + def end(self): + mc.progressBar(self.mainProgressBar, edit=True, endProgress=True) + + +def objectExists(obj): + """ Check if object exists """ + if obj and len(obj): + return mc.objExists(obj[0]) + else: + return 0 + + +def addAtIndex(addTo, index, add): + """ Adds to source list at index. If list is empty, appends. """ + if len(addTo) <= index: + addTo.append(add) + else: + addTo[index] = add + + +def getClosestPointAndNormal(targetMesh, pointPos=(0, 0, 0)): + """ + (targetMesh, givenPointPosition) -> (MPoint, MVector, int) + + Returns a tuple containing the closest point on the mesh to the given point, + the normal at that point, and the ID of the face in which that point lies. + """ + pointPos = om.MPoint(pointPos) + sel = om.MSelectionList() + sel.add(targetMesh) + fnMesh = om.MFnMesh(sel.getDagPath(0)) + + pointAndNormal = fnMesh.getClosestPointAndNormal(pointPos, space=om.MSpace.kWorld) + return pointAndNormal + + +def getClosestVertexAndNormal(targetMesh, pointPos=(0, 0, 0)): + # type: (str, om.MVector | om.MPoint) -> tuple[om.MPoint, float, str, int, om.MVector] + """ + returns: (vertPos, vertDist, vertName, vertIndex, faceNormal) + + Returns a tuple containing the closest to the pointPos vert on the mesh, the distance + between this vert and pointPos, vertex name and vertex index + """ + pos = om.MPoint(pointPos) + sel = om.MSelectionList() + sel.add(targetMesh) + fn_mesh = om.MFnMesh(sel.getDagPath(0)) + + index = fn_mesh.getClosestPoint(pos, space=om.MSpace.kWorld)[1] + faceNormal = fn_mesh.getPolygonNormal(index, space=om.MSpace.kWorld) + faceVerts = fn_mesh.getPolygonVertices(index) + + vertexDistances = ((vertex, fn_mesh.getPoint(vertex, om.MSpace.kWorld).distanceTo(pos)) + for vertex in faceVerts) + vertIndex, vertDistance = min(vertexDistances, key=lambda t: t[1]) + vertPosition = fn_mesh.getPoint(vertIndex, om.MSpace.kWorld) + vertName = "{}.vtx[{}]".format(targetMesh, vertIndex) + + return (vertPosition, vertDistance, vertName, vertIndex, faceNormal) + + +def resetAttributes(inputCurve, inputGeo): + # type: (str, str) -> None + """ + Resets attributes to a default state suitable for mirroring and orientation adjustment + """ + from .. import core + + if mc.attributeQuery('lengthDivisions', n=inputCurve, ex=1): + mc.setAttr(inputCurve + '.lengthDivisions', 10) + try: + mc.setAttr(inputCurve + '.widthDivisions', 3) + except BaseException: + mc.setAttr(inputCurve + '.widthDivisions', 5) + if mc.attributeQuery('Profile', n=inputCurve, ex=1): + mc.setAttr(inputCurve + '.Profile', 0) + core.updateLattice("0, 0.5, 0.333, 0.5, 0.667, 0.5, 1, 0.5", inputCurve) + if mc.attributeQuery('Twist', n=inputCurve, ex=1): + mc.setAttr(inputCurve + '.Twist', 0) + # if mc.attributeQuery('invTwist', n=inputCurve, ex=1): + # mc.setAttr(inputCurve + '.invTwist', 0) + if mc.attributeQuery('Length', n=inputCurve, ex=1): + targetNode = mc.ls(mc.listHistory(inputGeo, ac=1, il=0), typ='curveWarp')[0] + core.attributes.resetMultiInst(targetNode, 'scaleCurve') + core.attributes.resetMultiInst(targetNode, 'twistCurve') + + +def resetAndReturnAttrs(curve): + # type: (str) -> tuple[str, om.MVector, int, int, float, float, list[list[str]]] + """ + Resets the attributes on a curve and returns original values as a list: + + returns [curve, origVec, lengthDivisions, widthDivisions, twist, invTwist, graphs] + """ + from .. import core + crvAttr = core.attributes.getAttr(curve) + lengthDivisions = crvAttr['lengthDivisions'] if 'lengthDivisions' in crvAttr else None + widthDivisions = crvAttr['widthDivisions'] if 'widthDivisions' in crvAttr else None + twist = crvAttr['Twist'] if 'Twist' in crvAttr else None + invTwist = crvAttr['invTwist'] if 'invTwist' in crvAttr else None + + if lengthDivisions and lengthDivisions > 10: + mc.setAttr(curve + '.lengthDivisions', 10) + if widthDivisions and widthDivisions > 2: + try: + mc.setAttr(curve + '.widthDivisions', 2) + except BaseException: + mc.setAttr(curve + '.widthDivisions', 4) + if twist and twist != 0: + mc.setAttr(curve + '.Twist', 0) + if invTwist and invTwist != 0: + mc.setAttr(curve + '.invTwist', 0) + + firstCv = '%s.cv[0]' % curve + pos = mc.pointPosition(firstCv) + geo = core.selectPart(2, True, curve)[0] + + graphs = None + if mc.attributeQuery('Length', n=curve, ex=1): + targetNode = mc.ls(mc.listHistory(geo, ac=1, il=0), typ='curveWarp')[0] + graphs = core.attributes.getMultiInst(curve) + core.attributes.resetMultiInst(targetNode, 'scaleCurve') + core.attributes.resetMultiInst(targetNode, 'twistCurve') + + origVec = getClosestPointAndNormal(geo, pos)[1] + return [curve, origVec, lengthDivisions, widthDivisions, twist, invTwist, graphs] + + +def getMiddleVertAndNormal(inputCurve, inputGeo, inputFirstCv): + # type: (str, str, om.MVector) -> tuple[om.MVector, om.MVector] + """Returns the middle profile vert of the card and normals of the flat card""" + + from .. import core + + # Getting original values + origAttrs = core.attributes.getAttr(inputCurve) + origGraphs = core.attributes.getMultiInst(inputCurve) + origLattice = core.getLatticeValues(inputCurve) + + # Reset attributes + resetAttributes(inputCurve, inputGeo) + + # Set a new temporary values + if mc.attributeQuery('Profile', n=inputCurve, ex=1): + mc.setAttr(inputCurve + ".Profile", 0) + if origLattice: + core.updateLattice("0, 0.5, 0.333, 0.5, 0.667, 0.5, 1, 0.5", inputCurve) + mc.setAttr(inputCurve + '.lengthDivisions', 10) + if mc.attributeQuery('Width', n=inputCurve, ex=1): + mc.setAttr(inputCurve + '.widthDivisions', 3) + else: + mc.setAttr(inputCurve + '.widthDivisions', 5) + + # Get closest vert and normal value + closestVertAndNormal = getClosestVertexAndNormal(inputGeo, inputFirstCv) + + # Get a position value for the closest vert + if mc.attributeQuery('Profile', n=inputCurve, ex=1): + profileScale = 2 * mc.getAttr(inputCurve + '.Width') + if mc.attributeQuery('scaleFactor', n=inputCurve, ex=1): + profileScale *= mc.getAttr(inputCurve + '.scaleFactor') + mc.setAttr(inputCurve + '.Profile', math.copysign(profileScale, origAttrs['Profile'])) + vertexPos = mc.pointPosition("{}.vtx[{}]".format(inputGeo, closestVertAndNormal[3])) + + # Set the original attributes back + core.attributes.setAttr(inputCurve, origAttrs) + if origGraphs: + for graph in origGraphs: + core.attributes.setMultiInst(inputCurve, graph) + if origLattice: + core.updateLattice(fromDouble2ToString(origLattice), inputCurve) + + return (om.MVector(vertexPos), closestVertAndNormal[4]) + + +def getUnitMult(): + mult = float(mc.convertUnit("1cm", fromUnit="cm", toUnit=mc.currentUnit(q=1, linear=1))) + print("Current Mult:", mult) + return mult + + +def polySelectSp(comps, obj=None): + # type: (list[str], str) -> list[str] + """polySelectSp wrapper""" + if MAYA_VER >= 2023: + return mc.ls(mc.polySelectSp(comps, q=1, loop=1), fl=1) + else: + return selectVertLoop(obj, comps) + + +def selectVertLoop(obj, comps): + # type: (str, list[str]) -> list[str] + """ + Returns vert loop list between two verts on the border + For older Maya versions that had polySelectSp fail do to that + """ + if len(comps) != 2: + raise Exception("Wrong number of input verts") + + allFaces = "{}.f[*]".format(obj) + + def expandToEdges(verts): + if MAYA_VER == 2018: + if isinstance(verts, set): + verts = list(verts) + return set(mc.ls(mc.polyListComponentConversion(verts, fv=1, te=1), fl=1)) + + def expandToVerts(edges): + if MAYA_VER == 2018: + if isinstance(edges, set): + edges = list(edges) + return set(mc.ls(mc.polyListComponentConversion(edges, fe=1, tv=1), fl=1)) + + perimeterEdges = set(mc.ls(mc.polyListComponentConversion(allFaces, ff=1, te=1, bo=1))) + if MAYA_VER == 2018: + perimeterEdges = list(perimeterEdges) + perimeterVerts = set(mc.ls(mc.polyListComponentConversion(perimeterEdges, fe=1, tv=1), fl=1)) + sourceVert = comps[0] # type: str + targetVert = comps[1] # type: str + pathOne = set() + pathTwo = set() + + # Initial expand to find two possible paths + initVerts = expandToVerts(expandToEdges(sourceVert)) + initVerts.remove(sourceVert) + pathOne.add(initVerts.pop()) + pathTwo.add(initVerts.pop()) + + # Check first path + limit = 1000 + count = 0 + currentVert_one = pathOne.copy().pop() + pathOne.add(sourceVert) + currentVert_two = pathTwo.copy().pop() + pathTwo.add(sourceVert) + while count < limit: + count += 1 + # Expand first path + toEdges_one = expandToEdges(currentVert_one) + toVerts_one = expandToVerts(toEdges_one) + nonPerimeterVerts_one = toVerts_one.intersection(perimeterVerts) # Filter out non-perimeter verts + nextVert_one = nonPerimeterVerts_one.difference(pathOne) + pathOne.update(nextVert_one) + currentVert_one = nextVert_one.pop() + if targetVert in pathOne: + return list(pathOne) + # Expand second path + toEdges_two = expandToEdges(currentVert_two) + toVerts_two = expandToVerts(toEdges_two) + nonPerimeterVerts_two = toVerts_two.intersection(perimeterVerts) # Filter out non-perimeter verts + nextVert_two = nonPerimeterVerts_two.difference(pathTwo) + pathTwo.update(nextVert_two) + currentVert_two = nextVert_two.pop() + if targetVert in pathTwo: + return list(pathTwo) + return None + + +def deleteKeysOnAllObjects(): + """Deletes all the keys on all nurbs curve in the scene""" + for obj in mc.ls(typ='nurbsCurve'): + deleteKeys(obj) + + +def deleteKeys(target): + # type: (list[str]|str) -> None + """Deletes keys from target curves and their CVs""" + mc.cutKey(target, cl=1, t=(), hi='both', cp=1, s=1) + + +def colorFrom255to1(clr): + if isinstance(clr, Iterable): + clr = list(clr) + for i in range(len(clr)): + if isinstance(clr[i], float) or isinstance(clr[i], int): + clr[i] = clr[i] / 255.0 + if clr: + return clr + else: + MESSAGE.warning("Invalid color conversion") + elif isinstance(clr, float) or isinstance(clr, int): + return clr / 255.0 + else: + MESSAGE.warning("Invalid color conversion") + + +def colorFrom1to255(clr): + if isinstance(clr, Iterable): + clr = list(clr) + for i in range(len(clr)): + if isinstance(clr[i], float) or isinstance(clr[i], int): + clr[i] = clr[i] * 255.0 + if clr: + return clr + else: + MESSAGE.warning("Invalid color conversion") + elif isinstance(clr, float) or isinstance(clr, int): + return clr * 255.0 + else: + MESSAGE.warning("Invalid color conversion") + + +def fromStringToDouble2(inputString): + splitPoints = inputString.split(',') + splitPoints = filter(None, splitPoints) + splitPoints = [float(point) for point in splitPoints] + arrangedValues = [[splitPoints[i], splitPoints[i + 1]] for i in range(0, len(splitPoints), 2)] + return arrangedValues + + +def fromDouble2ToString(inputList): + newString = '' + for i in range(len(inputList)): + newString += '%s,%s,' % (inputList[i][0], + inputList[i][1]) + return newString + + +def setDouble2Attr(node, attr, values): + for i in range(len(values)): + mc.setAttr('%s.%s[%s]' % (node, attr, i), values[i][0], values[i][1], typ='double2') + + +def convertInstanceToObj(objects=list()): + """ Accepts a list and converts any found instances to objects """ + convert = mc.menuItem('gsConvertInstances', q=1, rb=1) + finalList = list() + if not objects: + return finalList + for obj in objects: + if mc.attributeQuery('Orientation', n=obj, ex=1) and mc.connectionInfo(obj + '.Orientation', isSource=1): + LOGGER.info(obj + ' is skipped. It is a part of existing curveCard/Tube.') + continue + par = mc.listRelatives(mc.listRelatives(obj, pa=1, c=1)[0], pa=1, ap=1) + if len(par) > 1 and convert: + dup = mc.duplicate(obj) + mc.delete(obj) + mc.rename(dup[0], obj) + LOGGER.info(obj + ' is an instance. Converted to object.') + finalList.append(obj) + elif len(par) > 1 and not convert: + LOGGER.info(obj + ' is skipped. It is an instance.') + else: + finalList.append(obj) + mc.evalDeferred("print(' ')") + return finalList + + +def mergeDicts(dict1, dict2, rd=False): + """ Merge dictionaries and keep values of common keys in list. rd - remove duplicates """ + dict3 = dict() + dict3.update(dict1) + dict3.update(dict2) + for key, value in dict3.items(): + if key in dict1 and key in dict2: + if isinstance(dict1[key], list) and isinstance(dict2[key], list): + for geo in dict1[key]: + dict3[key].append(geo) + elif isinstance(dict1[key], list) and not isinstance(dict2[key], list): + dict3[key] = [dict3[key]] + dict1[key] + elif not isinstance(dict1[key], list) and isinstance(dict2[key], list): + dict3[key].append(dict1[key]) + else: + dict3[key] = [value, dict1[key]] + if rd: + for key in dict3: + if isinstance(dict3[key], list): + tmp = dict.fromkeys(dict3[key]) + dict3[key] = list(tmp) + return dict3 + + +def cleanDict(dict0): + """Removes objects from the dict if they were not found in the scene""" + for key in dict0: + tempList = list() + for geo in dict0[key]: + if mc.objExists(geo): + tempList = tempList + [geo] + dict0[key] = tempList + return dict0 + + +def getShader(inputGeo): + """ Returns a dict of shadingEngines from list of geo in format {shader: [geo1,geo2...]} """ + + inputGeo = mc.filterExpand(inputGeo, sm=12) + if not inputGeo: + return {} + + shaderDict = dict() + for geo in inputGeo: + dag = mc.ls(geo, dag=1, s=1) + shader = mc.listConnections(dag, d=1, s=1, t='shadingEngine')[0] + shaderDict.setdefault(shader, []) + shaderDict[shader].append(geo) + + return shaderDict + + +def connectMessage(source, target, message): + """Creates messages in both nodes based on message name and connects them source->target\n + Target node will be a multi-instance message""" + if source == target: + return + if not mc.attributeQuery(message, n=source, ex=1): + mc.addAttr(source, ln=message, at='message', k=0) + if mc.attributeQuery(message, n=target, ex=1) and not mc.attributeQuery(message, n=target, m=1): + mc.deleteAttr('{}.{}'.format(target, message)) + if not mc.attributeQuery(message, n=target, ex=1): + mc.addAttr(target, ln=message, at='message', k=0, m=1, im=0) + mc.connectAttr('{}.{}'.format(source, message), '{}.{}'.format(target, message), na=1) + + +def getMod(): + """ Get modifiers (Shift, Alt, Ctrl and '+' combinations) """ + mods = mc.getModifiers() + if (mods & 1) > 0 and (mods & 4) > 0 and (mods & 8) > 0: + return 'Shift+Ctrl+Alt' + if (mods & 1) > 0 and (mods & 4) > 0: + return 'Shift+Ctrl' + if (mods & 1) > 0 and (mods & 8) > 0: + return 'Shift+Alt' + if (mods & 4) > 0 and (mods & 8) > 0: + return 'Ctrl+Alt' + if (mods & 1) > 0: + return 'Shift' + if (mods & 4) > 0: + return 'Ctrl' + if (mods & 8) > 0: + return 'Alt' + + +def getHotkeyMod(hk, key): + # type: (int, str) -> bool + """Gets key modifiers and checks if hotkey was used""" + return True if hk == 2 or (hk is None and getMod() == key) else False + + +def fixDuplicateNames(nodes=None): + """ Fixes duplicate names conflicts in input nodes or all nodes in the scene """ + if not nodes: + nodes = mc.ls() + duplicates = [node for node in nodes if '|' in node] + duplicates.sort(key=lambda obj: obj.count('|'), reverse=True) + newNames = [] + if not duplicates: + return [] + for obj in duplicates: + regEx1 = re.compile("[^|]*$").search(obj) + shortName = regEx1.group(0) + regEx2 = re.compile(".*[^0-9]").match(shortName) + if regEx2: + stripSuffix = regEx2.group(0) + else: + stripSuffix = shortName + newName = mc.rename(obj, (stripSuffix + "#")) + newNames.append(newName) + return newNames + + +def checkIfBezier(inputCurve): + if mc.nodeType(mc.listRelatives(inputCurve, c=1, pa=1)) == "bezierCurve": + origSel = mc.ls(sl=1) + mc.select(inputCurve, r=1) + result = mc.bezierCurveToNurbs() + origSel.append(result[0]) + if result: + mc.rebuildCurve(result, kr=2, kcp=1, fr=1) + mc.select(origSel, r=1) + return result + return inputCurve + + +def convertBezierToNurbs(): + sel = mc.ls(sl=1, dag=1, typ="bezierCurve") + for crv in sel: + oldConnections = mc.listConnections(crv, c=1, p=1, scn=1) + try: + mc.disconnectAttr(oldConnections[0], oldConnections[1]) + mc.select(crv, r=1) + result = mc.bezierCurveToNurbs() + mc.connectAttr(oldConnections[0], oldConnections[1]) + if result: + mc.rebuildCurve(result, kr=2, kcp=1, fr=1) + except Exception as e: + LOGGER.exception(e) + LOGGER.info("Curve %s was not converted" % crv) + + +@undo +def resetSingleGraph(graph, target=None): + sel = target if target else mc.filterExpand(mc.ls(sl=1, tr=1), sm=9) + if not sel: + sel = mc.filterExpand(mc.listRelatives(mc.ls(sl=1, o=1), p=1, pa=1), sm=9) + if not sel: + return + for curve in sel: + if mc.attributeQuery('Length', n=curve, ex=1) and mc.connectionInfo(curve + '.Length', isSource=1): + warp = mc.listConnections(curve + '.Length') + if warp: + from .. import core + from .wrap import WIDGETS + if graph == 'scale' or graph == 'width': + core.attributes.resetMultiInst(warp[0], 'scaleCurve') + WIDGETS['scaleCurve'].resetGraph() + if graph == 'twist': + core.attributes.resetMultiInst(warp[0], 'twistCurve') + WIDGETS['twistCurve'].resetGraph() + + +def fixBrokenGraphs(): + sel = mc.filterExpand(mc.ls(typ='nurbsCurve'), sm=9) + for curve in sel: + if mc.attributeQuery('Length', n=curve, ex=1) and mc.connectionInfo(curve + '.Length', isSource=1): + warp = mc.listConnections(curve + '.Length') + if warp: + from .. import core + correctScale = None + correctTwist = None + if mc.attributeQuery('scaleCurve', n=curve, ex=1) and mc.attributeQuery('scaleCurve', n=warp[0], ex=1): + correctScale = mc.getAttr(curve + '.scaleCurve') + if mc.attributeQuery('twistCurve', n=curve, ex=1) and mc.attributeQuery('twistCurve', n=warp[0], ex=1): + correctTwist = mc.getAttr(curve + '.twistCurve') + if correctScale: + core.attributes.resetMultiInst(warp[0], 'scaleCurve') + setDouble2Attr(warp[0], 'scaleCurve', fromStringToDouble2(correctScale)) + if correctTwist: + core.attributes.resetMultiInst(warp[0], 'twistCurve') + setDouble2Attr(warp[0], 'twistCurve', fromStringToDouble2(correctTwist)) + + +@undo +def convertToNewLayerSystem(): + """Converts from the old layer system to the new one""" + layers = mc.ls(typ='displayLayer') + from .. import core + if mc.objExists(core.toggleColor.STORAGE_NODE): + mc.delete(core.toggleColor.STORAGE_NODE) + for layer in layers: + if 'curveGrp_' in layer: + connection = mc.listConnections(layer + '.identification', s=1, et=1, t='displayLayerManager', p=1) + if connection: + connectionOut = mc.listConnections(layer + '.drawInfo', d=1, scn=1, et=1, t='transform', p=1) + if connectionOut: + newNode = mc.createNode('displayLayer') + mc.copyAttr(layer, newNode, ic=0, oc=1, v=1, at=['drawInfo']) + mc.delete(layer) + mc.rename(newNode, layer) + + +def createNewDisplayLayer(name=None, objects=None): + # type: (str, list[str]) -> str + """ + Creates a new display layer independent from Maya display layer system. + + name: The name of the Layer + objects: The list of objects to add to the Layer + + returns: Name of the new layer node + """ + newLayer = mc.createNode('displayLayer') + if name: + newLayer = mc.rename(newLayer, name) + if objects: + mc.editDisplayLayerMembers(name, objects, nr=1) + return newLayer + + +def getFormattedLayerNames(collectionID, layerID): + # type: (int, int) -> tuple[str, str, str] + """Gets layer names from the current collection id and layer id""" + id = '' + if collectionID: + id = '%s_' % collectionID + grpCurve = 'curveGrp_%s%s_Curve' % (id, layerID) + grpGeo = 'curveGrp_%s%s_Geo' % (id, layerID) + grpInst = 'curveGrp_%s%s_Inst' % (id, layerID) + return (grpCurve, grpGeo, grpInst) + + +def getFormattedCollectionByID(id): + # type: (int) -> str + """Returns a formatted collection ID, suitable for string concat""" + collection = '' + if int(id) > 0: + collection = '%s_' % id + return collection + + +def getCollectionsSet(): + # type: () -> set + """Returns active collections indexes in a set""" + allLayers = mc.ls(typ='displayLayer') + collections = set() + for layer in allLayers: + if 'curveGrp_' in layer and ('_Geo' in layer or '_Curve' in layer or '_Inst' in layer): + c = layer.split('_') + if len(c) == 4: + collections.add(c[1]) + return collections + + +def AOToggle(): + if (mc.getAttr('hardwareRenderingGlobals.ssaoEnable')): + mc.setAttr('hardwareRenderingGlobals.ssaoEnable', 0) + else: + mc.setAttr('hardwareRenderingGlobals.ssaoEnable', 1) + + +def disableEcho(): + """ Disable Echo All Commands """ + if mc.commandEcho(q=1, st=1) and not mc.optionVar(q='gsEchoAllCommands'): + mc.commandEcho(st=0) + LOGGER.info('Echo All Commands function is disabled for performance reasons.') + LOGGER.info('To disable this functionality, set internalVariable "gsEchoAllCommands" to 1') + LOGGER.info('Example(MEL): optionVar -iv "gsEchoAllCommands" 1') + + +def checkNativePlugins(plugInList, myPluginName): + """Checks if all the required NATIVE maya plugins are loaded""" + for plugin in plugInList: + if mc.pluginInfo(plugin, q=1, loaded=1) == 0: + try: + mc.loadPlugin(plugin, qt=1) + LOGGER.info('{0} plug-in was loaded successfully!'.format(plugin)) + except Exception as e: + message = 'Warning!\n\nMaya native Plug-in "{0}" was not detected and can\'t be activated.\ + \nYou can\'t use some of the functionality of {1} without {0} plug-in.\ + \n\nTry to restart Maya and check your Maya installation.\ + \n\nNOTE: This error can also be triggered if you have maya Plug-in Manager opened. Try to close it and repeat.'\ + .format(plugin, myPluginName) + mc.confirmDialog( + title='{0} Plug-in Not Detected'.format(plugin), + message=message, + button=['OK'], + defaultButton='OK', + cancelButton='OK', + dismissString='OK', + icn='critical') + errorMessage = 'Maya "{0}" plug-in can\'t be loaded. Please check if your Maya install is correct.'.format(plugin) + LOGGER.exception(e) + MESSAGE.warning(errorMessage) + + +def loadCustomPlugin(plugin): + """Checks if all the required CUSTOM maya plugins are loaded""" + if mc.pluginInfo(plugin, q=1, loaded=1) == 0: + split = os.path.split(plugin) + try: + mc.loadPlugin(plugin, qt=1) + LOGGER.info("{0} plug-in was loaded successfully!".format(split[-1])) + return True + except BaseException: + return False + else: + return True + + +def userSetup(): + """ Rewrite userSetup file """ + fileName = mc.internalVar(usd=1) + 'userSetup.mel' + if os.path.exists(fileName): + fileID = open(fileName, 'r') + allLines = fileID.readlines() + fileID.close() + newList = list() + lineDetected = 0 + # Remove old code if any exist + for i in range(len(allLines)): + if 'gs_curvetools' not in allLines[i]: + newList.append(allLines[i]) + else: + lineDetected = 1 + # Write to userSetup if needed + if lineDetected == 1: + LOGGER.info('userSetup.mel was modified successfully!') + newFileID = open(fileName, 'w') + newFileID.writelines(newList) + newFileID.close() + + +def getDPI(): + return mc.mayaDpiSetting(q=1, sd=1) + + +def openDocs(): + """ Open User Documentation """ + if OS == "mac": + os.system('open https://gs-curvetools.readthedocs.io/') + return 1 + os.system('start https://gs-curvetools.readthedocs.io/') + + +def openLink(link): + """ Open User Documentation """ + if OS == "mac": + os.system('open %s' % link) + return 1 + os.system('start %s' % link) diff --git a/Scripts/Modeling/Edit/gs_curvetools/utils/wrap.py b/Scripts/Modeling/Edit/gs_curvetools/utils/wrap.py new file mode 100644 index 0000000..3bdb2e3 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/utils/wrap.py @@ -0,0 +1,1319 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +import re +from functools import partial as pa +from imp import reload + +import maya.cmds as mc +import maya.OpenMayaUI as omui +from PySide2 import QtCore, QtGui, QtWidgets +from shiboken2 import wrapInstance + +from . import style, tooltips, utils + +TOOLTIPS_DICT = tooltips.processTooltips() + +reload(utils) +reload(style) +reload(tooltips) + +# Holds the names and pointers to all controls +try: + bool(WIDGETS) # type: ignore # pylint: disable=used-before-assignment +except BaseException: + WIDGETS = {} + + +def getUniqueName(name): + from ..main import MAIN_WINDOW_NAME + return (MAIN_WINDOW_NAME + "_" + name) + + +def wrapControl(name): + name = omui.MQtUtil.findControl(name) + return wrapInstance(int(name), QtWidgets.QWidget) + + +def separator(): + name = omui.MQtUtil.findControl(mc.separator(st='in')) + return wrapInstance(int(name), QtWidgets.QWidget) + +# Layouts + + +class Layout(QtWidgets.QWidget): + + def __init__(self, parent, objName='', spacing=2, + margin=0, margins=[0, 0, 0, 0], vis=True): + super(Layout, self).__init__() + if objName: + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + self.__parent = parent + self.__spacing = spacing + self.__margin = margin + self.__margins = margins + self.__visibility = vis + + def __enter__(self): + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(*self.__margins) + layout.setMargin(self.__margin) + layout.setSpacing(self.__spacing) + layout.addWidget(self) + self.setVisible(self.__visibility) + return self + + def __exit__(self, *_): + self.__parent.addWidget(self) + + +class Row(QtWidgets.QWidget): + + def __init__(self, parent, objName='', spacing=2, margins=[0, 0, 0, 0]): + super(Row, self).__init__() + if objName: + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.__parent = parent + self.__spacing = spacing + self.__margins = margins + + def __enter__(self): + row = QtWidgets.QHBoxLayout(self) + row.setSpacing(self.__spacing) + row.setContentsMargins(*self.__margins) + return self + + def __exit__(self, *_): + self.__parent.addWidget(self) + + +class Column(QtWidgets.QWidget): + + def __init__(self, parent, objName='', spacing=2, margins=[0, 0, 0, 0]): + super(Column, self).__init__() + if objName: + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + self.__parent = parent + self.__spacing = spacing + self.__margins = margins + + def __enter__(self): + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(self.__spacing) + layout.setContentsMargins(*self.__margins) + return self + + def __exit__(self, *_): + self.__parent.addWidget(self) + + +class Frame(QtWidgets.QFrame): + + def __init__(self, parent, objName='', label=None, spacing=2, margins=[0, 0, 0, 0]): + super(Frame, self).__init__() + + self.objName = '' + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.setAutoFillBackground(True) + self.setGeometry(0, 0, 0, 0) + + self.mainParent = parent + self.frameSpacing = style.scale(spacing) + self.frameMargins = style.scale(margins) + self.frameLabel = label + + self.mainLayout = QtWidgets.QVBoxLayout(self) + self.mainLayout.setSpacing(self.frameSpacing) + self.mainLayout.setContentsMargins(0, 0, 0, 0) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def eventFilter(self, object, event): + if object is not self.frameButton and event.type() == QtCore.QEvent.ToolTip: + self.enableTooltip(False) + elif object is self.frameButton and event.type() == QtCore.QEvent.ToolTip: + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + return super(Frame, self).eventFilter(object, event) + + def __enter__(self): + # Layout Button + self.frameButton = Button(self.layout()) + if self.frameLabel: + self.frameButton.setLabel(self.frameLabel) + self.setCollapsible(True) + self.frameButton.clicked.connect(pa(self.toggleCollapsed)) + + # Main Frame for Contents + self.frameWidget = QtWidgets.QWidget() + self.frameWidget.setContentsMargins(*self.frameMargins) + self.frameLayout = QtWidgets.QVBoxLayout(self.frameWidget) + self.frameLayout.setSpacing(self.frameSpacing) + self.frameLayout.setContentsMargins(*self.frameMargins) + self.frameWidget.installEventFilter(self) + self.frameButton.installEventFilter(self) + + self.frameWidget.setHidden(True) + self.layout().addWidget(self.frameWidget) + return self + + def __exit__(self, *_): + self.mainParent.addWidget(self) + + def getFrameLayout(self): + return self.frameLayout + + def setCollapsed(self, hidden=False): + self.frameWidget.setHidden(hidden) + self.frameButton.setChecked(not hidden) + + def toggleCollapsed(self): + palette = QtGui.QPalette() + if self.frameWidget.isVisible(): + self.setFrameStyle(self.NoFrame) + color = QtGui.QColor(0, 0, 0, 0) + palette.setColor(QtGui.QPalette.Background, color) + self.setPalette(palette) + self.frameWidget.setHidden(True) + self.frameButton.setChecked(False) + else: + self.setFrameStyle(self.Panel | self.Sunken) + color = QtGui.QColor(0, 0, 0, 30) + palette.setColor(QtGui.QPalette.Background, color) + self.setPalette(palette) + self.frameWidget.setHidden(False) + self.frameButton.setChecked(True) + + def setCollapsible(self, collapsible): + if collapsible: + self.frameButton.setButtonStyle('frame-button') + self.frameButton.setCheckable(True) + self.frameButton.setChecked(False) + self.frameButton.blockSignals(False) + else: + self.frameButton.setButtonStyle('frame-button-not-collapsible') + self.setCollapsed(False) + self.frameButton.setCheckable(False) + self.frameButton.setChecked(False) + self.frameButton.blockSignals(True) + +# Menu Items: + + +class Menu: + + def __init__(self, label, parent, tearable=True, collapsible=False): + self.label = label + self.parent = parent + self.tearable = tearable + self.collapsible = collapsible + + def __enter__(self): + self.menu = QtWidgets.QMenu(self.label) + self.menu.setToolTipsVisible(True) + self.menu.setTearOffEnabled(self.tearable) + self.menu.setSeparatorsCollapsible(self.collapsible) + return self.menu + + def __exit__(self, *_): + self.parent.addMenu(self.menu) + + +class ActionGroup: + + def __init__(self, objName, parent): + self.objName = objName + self.parent = parent + + def __enter__(self): + self.group = QtWidgets.QActionGroup(self.parent) + if self.objName: + self.group.setObjectName(getUniqueName(self.objName)) + WIDGETS[self.objName] = self.group + return self.group + + def __exit__(self, *_): + pass + + +class MenuItem(QtWidgets.QAction): + + def __init__(self, objName, label, parent, checkable=False, checked=False, collection=None): + super(MenuItem, self).__init__(label, parent) + self.objName = objName + self.setCheckable(checkable) + if checked: + self.setChecked(checked) + if self.objName: + self.setObjectName(getUniqueName(self.objName)) + WIDGETS[self.objName] = self + if collection: + self.setActionGroup(collection) + parent.addAction(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + +def mayaSlider(slider, objName='', layout=None): # TODO: Need to refactor these function to use one class for wrapping + slider = wrapInstance(int(omui.MQtUtil.findControl(slider)), QtWidgets.QWidget) + if objName: + slider.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = slider + if layout: + layout.addWidget(slider) + return slider + + +# Buttons + +class Button(QtWidgets.QPushButton): # Maybe change back to the mode without inheritance + """ Creates a normal button """ + markingPixmap = QtGui.QPixmap(utils.getFolder.icons() + 'marking.png') + modPixmap = QtGui.QPixmap(utils.getFolder.icons() + 'mod.png') + resetPixmap = QtGui.QPixmap(utils.getFolder.icons() + 'reset.png') + + def __init__(self, layout=None, objName=''): + super(Button, self).__init__() + self.objName = '' + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + self.buttonStyle = '' + self.buttonState = 0 + self.buttonLabels = None + + if layout: + layout.addWidget(self) + + self.setButtonStyle() + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def setLabel(self, text, margins=[0, 0, 0, 0], lineHeight=80): + text = self._formatText(text, lineHeight=lineHeight) + + self.label = QtWidgets.QLabel(text, self) + + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(*style.scale(margins)) + layout.addWidget(self.label) + self.setLabelStyle() + + def setLabelStyle(self, labelStyle='normal'): + self.font = QtGui.QFont('Roboto') + self.label.setFont(self.font) + if labelStyle == 'normal': + self.setFontSize(11) + elif labelStyle == 'small': + self.setFontSize(10, False) + + def setButtonStyle(self, buttonStyle='normal'): + if buttonStyle == 'normal': + self.setStyleSheet(style.buttonNormal) + self.buttonStyle = 'normal' + elif buttonStyle == 'active-orange': + self.setStyleSheet(style.buttonActiveOrange) + self.buttonStyle = 'active-orange' + elif buttonStyle == 'active-blue': + self.setStyleSheet(style.buttonActiveBlue) + self.buttonStyle = 'active-blue' + elif buttonStyle == 'active-white': + self.setStyleSheet(style.buttonActiveWhite) + self.buttonStyle = 'active-white' + elif buttonStyle == 'small': + self.setStyleSheet(style.smallNormal) + self.buttonStyle = 'small' + elif buttonStyle == 'small-filled': + self.setStyleSheet(style.smallFilled) + self.buttonStyle = 'small-filled' + elif buttonStyle == 'small-no-border': + self.setStyleSheet(style.smallNoBorder) + self.buttonStyle = 'small-no-border' + elif buttonStyle == 'slider-label': + self.setStyleSheet(style.sliderLabel) + self.buttonStyle = 'slider-label' + elif buttonStyle == 'icon': + self.setStyleSheet(style.buttonIcon) + self.buttonStyle = 'icon-button' + elif buttonStyle == 'frame-button': + self.setStyleSheet(style.frameButton) + self.buttonStyle = 'frame-button' + elif buttonStyle == 'frame-button-not-collapsible': + self.setStyleSheet(style.frameButtonNotCollapsable) + self.buttonStyle = 'frame-button-not-collapsible' + elif buttonStyle == 'small-compound-top-left': + self.setStyleSheet(style.smallCompoundTopLeft) + self.buttonStyle = 'small-compound-top-left' + elif buttonStyle == 'small-compound-top-right': + self.setStyleSheet(style.smallCompoundTopRight) + self.buttonStyle = 'small-compound-top-right' + elif buttonStyle == 'small-filled-compound-bottom': + self.setStyleSheet(style.smallFilledCompoundBottom) + self.buttonStyle = 'small-filled-compound-bottom' + + def setIcon(self, icoType): + iconMod = self.modPixmap + iconMarking = self.markingPixmap + self.modLabelMod = QtWidgets.QLabel(self) + self.modLabelMarking = QtWidgets.QLabel(self) + + mult = 7 + iconMod = iconMod.scaled(style.scale(mult), style.scale(mult), QtCore.Qt.KeepAspectRatio) + self.modLabelMod.setContentsMargins(*style.scale([2, 2, 0, 0])) + self.modLabelMod.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) + self.modLabelMod.setPixmap(iconMod) + self.modLabelMod.setVisible(False) + + iconMarking = iconMarking.scaled(style.scale(mult), style.scale(mult), QtCore.Qt.KeepAspectRatio) + self.modLabelMarking.setContentsMargins(*style.scale([2, 2, 0, 0])) + self.modLabelMarking.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) + self.modLabelMarking.setPixmap(iconMarking) + self.modLabelMarking.setVisible(False) + + if icoType == 'marking-top': + self.modLabelMarking.setVisible(True) + elif icoType == 'marking-bottom': + self.modLabelMarking.setVisible(True) + self.modLabelMarking.move(*style.scale([0, 16])) + elif icoType == 'mod-top': + self.modLabelMod.setVisible(True) + elif icoType == 'mod-bottom': + self.modLabelMod.setVisible(True) + self.modLabelMod.move(*style.scale([0, 17])) + elif icoType == 'both': + self.modLabelMod.setVisible(True) + self.modLabelMarking.setVisible(True) + self.modLabelMarking.move(*style.scale([1, 13])) + elif icoType == 'reset': + iconReset = self.resetPixmap + iconReset = iconReset.scaled(style.scale(11), style.scale(11), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + super(Button, self).setIcon(iconReset) + else: + self.modLabelMod.setVisible(False) + self.modLabelMarking.setVisible(False) + + def changeIcon(self, icoType): + if icoType == 'marking': + self.modLabel.setPixmap(self.markingPixmap) + elif icoType == 'mod-top': + self.modLabel.setPixmap(self.modPixmap) + else: + self.modLabel.setPixmap(self.markingModPixmap) + + def setIconVisible(self, vis=True): + self.modLabel.setVisible(vis) + + def setFontSize(self, size, bold=True): + font = self.label.font() + font.setPixelSize(style.scale(size)) + if bold: + font.setBold(bold) + self.label.setFont(font) + + def setLabelOffset(self, offsetX, offsetY, width=None, height=None): + if not width: + width = self.label.geometry().width() + if not height: + height = self.label.geometry().height() + rect = QtCore.QRect(offsetX, offsetY, width, height) + self.label.setFrameRect(rect) + + def setWidthHeight(self, w=None, h=None): + if w: + self.setFixedWidth(style.scale(w)) + if h: + self.setFixedHeight(style.scale(h)) + + def changeText(self, *_): + rect = self.rect().width() + _min = style.scale(75) + _max = style.scale(115) + if rect < _min and self.buttonState != 0: + text = self._formatText(self.buttonLabels[0]) + self.label.setText(text) + self.buttonState = 0 + elif _min <= rect < _max and self.buttonState != 1: + text = self._formatText(self.buttonLabels[1]) + self.label.setText(text) + self.buttonState = 1 + elif rect >= _max and self.buttonState != 2 and len(self.buttonLabels) > 2: + text = self._formatText(self.buttonLabels[2]) + self.label.setText(text) + self.buttonState = 2 + + def setValue(self, value): + if self.isCheckable(): + self.setChecked(bool(value)) + + def addParent(self, parent, stretch=0): + parent.addWidget(self, stretch) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def _formatText(self, text, lineHeight=80): + if lineHeight == 100: + return text + if isinstance(text, list): + self.resizeEvent = self.changeText + self.buttonLabels = text + text = text[0] + text = '{0}
'.format(text, lineHeight) + return text + + +class IconCheckButton(QtWidgets.QPushButton): + + def __init__(self, parent, objName=''): + super(IconCheckButton, self).__init__() + self.setContentsMargins(0, 0, 0, 0) + self.setStyleSheet(style.buttonIcon) + self.objName = '' + if objName: + self.objName = objName + WIDGETS[objName] = self + self.setObjectName(getUniqueName(objName)) + self.setCheckable(True) + self.clicked.connect(self.toggleIcon) + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def setChecked(self, checked): + super(IconCheckButton, self).setChecked(checked) + self.toggleIcon() + + def setIcons(self, checked, unchecked): + self.icons = [checked, unchecked] + self.switchToIcon(0) + + def switchToIcon(self, iconId): + icon = QtGui.QIcon(self.icons[iconId]) + self.setIcon(icon) + + def setIconWidthHeight(self, w, h): + self.setFixedSize(QtCore.QSize(style.scale(w), style.scale(h))) + self.setIconSize(QtCore.QSize(style.scale(w), style.scale(h))) + + def toggleIcon(self): + if self.isChecked(): + self.switchToIcon(0) + else: + self.switchToIcon(1) + +# Layers + + +class Layer(QtWidgets.QPushButton): + + LAYERS = 20 + + def __init__(self, objName=None, layout=None): + super(Layer, self).__init__() + + self.buttonStyle = 'empty' + + self.setMinimumSize(1, 1) + self.setStyle() + + self.setCheckable(True) + + self.setAcceptDrops(True) + + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + if layout: + layout.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def setLabel(self, label): + self.buttonLabel = QtWidgets.QLabel(label, self) + self.buttonLabel.setStyleSheet(style.layerLabel) + self.buttonLabel.setAlignment(QtCore.Qt.AlignCenter) + self.buttonLabel.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + buttonLayout = QtWidgets.QHBoxLayout(self) + buttonLayout.setContentsMargins(0, 0, 0, 0) + buttonLayout.addWidget(self.buttonLabel) + + def changeLabel(self, text): + self.buttonLabel.setText(text) + + def setNumberOfLayers(self, num): + Layer.LAYERS = num + + # Set custom styles + def setStyle(self, buttonStyle='empty'): + if self.buttonStyle == 'custom': + return 0 + if buttonStyle == self.buttonStyle: + return + if buttonStyle == 'empty': + self.setStyleSheet(style.layer('rgba(0,0,0,0)')) + self.buttonStyle = 'empty' + elif buttonStyle == 'active': + self.setStyleSheet(style.layer('#db9456')) + self.buttonStyle = 'active' + elif buttonStyle == 'hidden': + self.setStyleSheet(style.layer('#a1a1a1')) + self.buttonStyle = 'hidden' + elif buttonStyle == 'curve': + self.setStyleSheet(style.layer('#5285a6')) + self.buttonStyle = 'curve' + elif buttonStyle == 'geo': + self.setStyleSheet(style.layer('#25934e')) + self.buttonStyle = 'geo' + elif buttonStyle == 'edit': + self.setStyleSheet(style.layer('\ + qlineargradient(\ + x1: 0, y1: 0, x2: 1, y2: 1,\ + stop: 0 #db9456, stop: 0.499 #db9456, stop: 0.501 red,stop: 1 red\ + );')) + self.buttonStyle = 'edit' + + def colorize(self, isColorized): + if isColorized: + self.buttonStyle = 'custom' + else: + self.buttonStyle = 'empty' + + def setLayout(self, layout): + layout.addWidget(self) + + def mousePressEvent(self, event): + super(Layer, self).mousePressEvent(event) + if event.button() == QtCore.Qt.MiddleButton: + drag = QtGui.QDrag(self) + mimeData = QtCore.QMimeData() + mimeData.setText(self.objectName()) + drag.setMimeData(mimeData) + drag.exec_() + + def dragEnterEvent(self, event): + super(Layer, self).dragEnterEvent(event) + if event.proposedAction() != QtCore.Qt.MoveAction: + return + if event.source().__class__ != Layer: + return + event.acceptProposedAction() + + def dropEvent(self, event): + super(Layer, self).dropEvent(event) + if event.proposedAction() != QtCore.Qt.MoveAction: + return + source = event.source() + if source != self and source.__class__ == Layer: + from .. import core + core.moveLayers(event.mimeData().text(), self.objectName()) + event.acceptProposedAction() + +# Sliders + + +class MayaSlider: + + """ Just a simple wrapper of normal Maya sliders """ + + def __init__(self, slider, objName='', layout=None): + self.slider = wrapInstance(int(omui.MQtUtil.findControl(slider)), QtWidgets.QWidget) + if objName: + self.slider.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self.slider + if layout: + layout.addWidget(self.slider) + + +class ControlSlider(QtWidgets.QWidget): + + linkIconTop = QtGui.QPixmap(utils.getFolder.icons() + 'marking.png') + linkIconBottom = QtGui.QPixmap(utils.getFolder.icons() + 'link_bottom.png') + + def __init__(self, parent=None, objName='', typ='int'): + super(ControlSlider, self).__init__() + + self.objName = '' + if objName: + self.objName = objName + self.attributeName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.minimum = self.fieldMinimum = 0 + self.maximum = self.fieldMaximum = 0 + + self.typ = typ + + self.layout = QtWidgets.QHBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + + self.button = Button(self.layout) + self.button.setButtonStyle('slider-label') + self.button.setCheckable(True) + self.button.setAutoFillBackground(True) + + if self.typ == 'int': # Possibly replace with native sliders for better functionality + m_slider = mc.intSliderGrp(cw=[(1, 45), (2, 1)], adj=2, f=1) + else: + m_slider = mc.floatSliderGrp(cw=[(1, 45), (2, 1)], adj=2, f=1) + + self.slider = wrapInstance(int(omui.MQtUtil.findControl(m_slider)), QtWidgets.QWidget) + self.layout.addWidget(self.slider) + + self.m_slider = self.slider.objectName() + + if parent: + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.button.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.button.setToolTip('') + + def setLabel(self, label): + self.button.setLabel(label) + self.button.label.setAlignment(QtCore.Qt.AlignRight) + self.button.setFixedWidth(style.scale(55)) + + def setLabelWidth(self, width): + self.button.setFixedWidth(style.scale(width)) + + def setValue(self, value): + if self.typ == 'int': + mc.intSliderGrp(self.m_slider, e=1, v=value) + else: + mc.floatSliderGrp(self.m_slider, e=1, v=value) + + def getValue(self): + if self.typ == 'int': + return mc.intSliderGrp(self.m_slider, q=1, v=1) + else: + return mc.floatSliderGrp(self.m_slider, q=1, v=1) + + def setDragCommand(self, cmd): + self.dragCommand = cmd + if self.typ == 'int': + mc.intSliderGrp(self.m_slider, e=1, dc=self.dragCommand) + else: + mc.floatSliderGrp(self.m_slider, e=1, dc=self.dragCommand) + + def setReleaseCommand(self, cmd): + def releaseCommand(*_): + self.dragCommand() + cmd() + if self.typ == 'int': + mc.intSliderGrp(self.m_slider, e=1, cc=releaseCommand) + else: + mc.floatSliderGrp(self.m_slider, e=1, cc=releaseCommand) + + def getAttributeName(self): + return self.attributeName + + def setMinMax(self, minimum, maximum): + self.minimum = minimum + self.maximum = maximum + if self.typ == 'int': + mc.intSliderGrp(self.m_slider, e=1, + min=self.minimum, max=self.maximum, + fmn=self.minimum, fmx=self.maximum) + else: + mc.floatSliderGrp(self.m_slider, e=1, + min=self.minimum, max=self.maximum, + fmn=self.minimum, fmx=self.maximum) + + def setFieldMinMax(self, minimum, maximum): + self.fieldMinimum = minimum + self.fieldMaximum = maximum + if self.typ == 'int': + mc.intSliderGrp(self.m_slider, e=1, fmn=self.fieldMinimum, fmx=self.fieldMaximum) + else: + mc.floatSliderGrp(self.m_slider, e=1, fmn=self.fieldMinimum, fmx=self.fieldMaximum) + + def setPrecision(self, precision): + if self.typ != 'int': + mc.floatSliderGrp(self.m_slider, e=1, pre=precision) + + def setStep(self, step): + if self.typ != 'int': + mc.floatSliderGrp(self.m_slider, e=1, s=step) + + def resetMinMax(self): + if self.typ == 'int': + mc.intSliderGrp(self.m_slider, e=1, + fmn=self.fieldMinimum, fmx=self.fieldMaximum, + min=self.minimum, max=self.maximum) + else: + mc.floatSliderGrp(self.m_slider, e=1, + fmn=self.fieldMinimum, fmx=self.fieldMaximum, + min=self.minimum, max=self.maximum) + +# Falloff curve graph + + +class FallOffCurve(QtWidgets.QWidget): + + def __init__(self, parent, objName='', attr=True): + super(FallOffCurve, self).__init__() + self.objName = objName + self.attr = attr + self.layout = QtWidgets.QVBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + if self.objName: + self.setObjectName(getUniqueName(self.objName)) + WIDGETS[self.objName] = self + if self.attr: + self.m_graph = mc.falloffCurveAttr(ccw=5, stg=0, h=220, hlc=[0.68, 0.68, 0.68]) + else: + self.m_graph = mc.falloffCurve(ccw=5, stg=0, h=220, hlc=[0.68, 0.68, 0.68]) + self.graph = wrapInstance(int(omui.MQtUtil.findControl(self.m_graph)), QtWidgets.QWidget) + self.graph.setStyleSheet('*{background-color:rgba(0,0,0,0);}') + self.layout.addWidget(self.graph) + + parent.addWidget(self) + + def connectGraph(self, path): + if self.attr: + mc.falloffCurveAttr(self.m_graph, e=1, at=path) + else: + mc.falloffCurve(self.m_graph, e=1, at=path) + + def changeCommand(self, command): + if self.attr: + mc.falloffCurveAttr(self.m_graph, e=1, cc=command) + else: + mc.falloffCurve(self.m_graph, e=1, cc=command) + + def getGraph(self): + if self.attr: + return mc.falloffCurveAttr(self.m_graph, q=1, asString=1) + else: + return mc.falloffCurve(self.m_graph, q=1, asString=1) + + def setGraph(self, string): + if self.attr: + mc.falloffCurveAttr(self.m_graph, e=1, asString=string) + else: + mc.falloffCurve(self.m_graph, e=1, asString=string) + + def resetGraph(self, custom=None): + defaults = r"0, 0.5, 0.333, 0.5, 0.667, 0.5, 1, 0.5" + if custom: + defaults = custom + if self.attr: + mc.falloffCurveAttr(self.m_graph, e=1, asString=defaults) + else: + mc.falloffCurve(self.m_graph, e=1, asString=defaults) + +# Color picker button + + +class ColorPicker(QtWidgets.QWidget): + + def __init__(self, objName='', parent=None): + super(ColorPicker, self).__init__() + + self.layout = QtWidgets.QVBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + + self.objName = '' + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.m_colorSlider = mc.colorSliderGrp(objName, rgb=[.5, .5, .5]) + + origSlider = wrapInstance(int(omui.MQtUtil.findControl(self.m_colorSlider)), QtWidgets.QWidget) + self.components = origSlider.children() + self.colorRect = self.components[1] + self.layout.addWidget(self.colorRect) + origSlider.setVisible(False) + + if parent: + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def addParent(self, parent, stretch=0): + parent.addWidget(self, stretch) + + def connectCommand(self, command): + mc.colorSliderGrp(self.m_colorSlider, e=1, cc=command) + + def connectDragCommand(self, command): + mc.colorSliderGrp(self.m_colorSlider, e=1, dc=command) + + def resetColors(self): + mc.colorSliderGrp(self.m_colorSlider, e=1, rgb=[0, 0, 0]) + + def setRGBColors(self, clr=[0, 0, 0]): + mc.colorSliderGrp(self.m_colorSlider, e=1, rgb=clr) + + def getRGBColors(self): + return mc.colorSliderGrp(self.m_colorSlider, q=1, rgb=1) + + def setHSVColors(self, clr=[0, 0, 0]): + mc.colorSliderGrp(self.m_colorSlider, e=1, hsv=clr) + + def getHSVColors(self): + return mc.colorSliderGrp(self.m_colorSlider, q=1, hsv=1) + +# Layer selection drop-down menu + + +class LayerSelector(QtWidgets.QComboBox): + + def __init__(self, objName, parent): + super(LayerSelector, self).__init__() + + self.objName = '' + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + symbols = [] + for c in range(ord('a'), ord('j') + 1): + symbols.append(chr(c).capitalize()) + numbers = [str(i) for i in range(10)] + numbers2 = [str(i) for i in range(20, 40)] + numbers3 = [str(i) for i in range(40, 80)] + + self.setContentsMargins(0, 0, 0, 0) + self.addItems(numbers + symbols + numbers2 + numbers3) + + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def setCurrentIndex(self, index): + # Disabling programmatic signal + self.blockSignals(True) + super(LayerSelector, self).setCurrentIndex(index) + self.blockSignals(False) + + def updateLayerList(self): + self.blockSignals(True) + from .. import core + if core.getOption('layerNumbersOnly'): + for i in range(10, 20): + self.removeItem(i) + self.insertItem(i, str(i)) + else: + letters = [chr(c).capitalize() for c in range(ord('a'), ord('j') + 1)] + for i in range(10, 20): + self.removeItem(i) + self.insertItem(i, str(letters[i - 10])) + self.blockSignals(False) + + def wheelEvent(self, event): + super(LayerSelector, self).wheelEvent(event) + + +class LayerCollectionWidget(QtWidgets.QComboBox): + + def __init__(self, objName, parent=None): + super(LayerCollectionWidget, self).__init__() + + self.objName = '' + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + self.setContentsMargins(0, 0, 0, 0) + if parent: + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def clear(self): + super(LayerCollectionWidget, self).clear() + self.addItem("Main") + + +# Line edit input field + + +class LineEdit(QtWidgets.QLineEdit): + + def __init__(self, objName, parent=None): + super(LineEdit, self).__init__() + + self.autoFormat = False + + self.objName = '' + if objName: + self.objName = objName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.setContentsMargins(0, 0, 0, 0) + + self.editingFinished.connect(self.formatText) + + if parent: + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def setAutoFormat(self, shouldFormat): + self.autoFormat = shouldFormat + + @utils.deferred + def formatText(self): + if self.autoFormat: + newName = self.text() + if newName: + if newName[0].isdigit(): + newName = newName[1:] + self.setText(re.sub(r"[^0-9a-zA-Z]+", "_", newName)) + +# Single float field + + +class FloatField(QtWidgets.QWidget): + + def __init__(self, objName, parent=None, attrName=''): + super(FloatField, self).__init__() + + self.layout = QtWidgets.QVBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + + self.objName = '' + if objName: + self.objName = objName + if not attrName: + self.attributeName = objName + else: + self.attributeName = attrName + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.floatField = mc.floatField(min=-10, max=10, pre=1, step=0.25, v=0) + + wrappedField = wrapInstance(int(omui.MQtUtil.findControl(self.floatField)), QtWidgets.QWidget) + self.layout.addWidget(wrappedField) + + if parent: + parent.addWidget(self) + + self.enableTooltip(mc.optionVar(q="GSCT_enableTooltips")) + + def enableTooltip(self, enable): + if enable and self.objName in TOOLTIPS_DICT: + self.setToolTip(TOOLTIPS_DICT[self.objName]) + else: + self.setToolTip('') + + def setDragCommand(self, cmd): + self.dragCommand = cmd + mc.floatField(self.floatField, e=1, dc=cmd) + + def setReleaseCommand(self, cmd): + def releaseCommand(*_): + self.dragCommand() + cmd() + mc.floatField(self.floatField, e=1, cc=releaseCommand) + + def getAttributeName(self): + return self.attributeName + + def getValue(self): + return mc.floatField(self.floatField, q=1, v=1) + + def setRange(self, minimum, maximum): + mc.floatField(self.floatField, e=1, min=minimum, max=maximum) + + def setPrecision(self, pre): + mc.floatField(self.floatField, e=1, pre=pre) + + def setStep(self, step): + mc.floatField(self.floatField, e=1, step=step) + + def setValue(self, value): + mc.floatField(self.floatField, e=1, v=value) + + +class IntField(QtWidgets.QWidget): + + def __init__(self, objName, parent): + super(IntField, self).__init__() + + self.layout = QtWidgets.QVBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + if objName: + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + + self.intField = mc.intField(min=-10, max=10, step=1, v=0) + + wrappedField = wrapInstance(int(omui.MQtUtil.findControl(self.intField)), QtWidgets.QWidget) + self.layout.addWidget(wrappedField) + + parent.addWidget(self) + + def setRange(self, minimum, maximum): + mc.intField(self.intField, e=1, min=minimum, max=maximum) + + def setStep(self, step): + mc.intField(self.intField, e=1, step=step) + + def setValue(self, value): + mc.intField(self.intField, e=1, v=value) + +# Label + + +class Label(QtWidgets.QLabel): + + def __init__(self, parent=None, objName=None, margins=[0, 0, 0, 0]): + super(Label, self).__init__() + + if objName: + self.setObjectName(getUniqueName(objName)) + WIDGETS[objName] = self + self.layout = QtWidgets.QHBoxLayout(self) + self.setContentsMargins(*style.scale(margins)) + if parent: + parent.addWidget(self) + + def addParent(self, parent, stretch=0): + parent.addWidget(self, stretch) + + def setLabel(self, text, lineHeight=100): + text = self.__formatText(text, lineHeight=lineHeight) + + self.setText(text) + + self.setAlignment(QtCore.Qt.AlignCenter) + self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + + self.setLabelStyle() + + def setLabelStyle(self, labelStyle='normal'): + self.font = QtGui.QFont('Roboto') + self.setFont(self.font) + if labelStyle == 'normal': + self.setFontSize(11) + elif labelStyle == 'small': + self.setFontSize(10, False) + + def setFontSize(self, size, bold=True): + font = self.font + font.setPixelSize(style.scale(size)) + if bold: + font.setBold(bold) + self.setFont(font) + + def __formatText(self, text, lineHeight=80): + if lineHeight == 100: + return text + if isinstance(text, list): + self.resizeEvent = self.changeText + self.buttonLabels = text + text = text[0] + text = '{0}
'.format(text, lineHeight) + return text + + +class UVStandardItem(QtGui.QStandardItem): + + def __init__(self, grpName='', crvName='', fontSize=12, setBold=False, color=[128, 128, 128, 255]): + super(UVStandardItem, self).__init__() + + self.fnt = QtGui.QFont('Open Sans') + self.fnt.setBold(setBold) + self.fnt.setPixelSize(style.scale(fontSize)) + self.curveName = crvName + self.groupName = grpName + + self.setEditable(False) + clr = QtGui.QColor(*color) + self.setForeground(clr) + self.setFont(self.fnt) + self.setText(self.groupName) + + def setBold(self, bold=True): + self.fnt.setBold(bold) + self.setFont(self.fnt) + + +class UVItemList(QtWidgets.QTreeView): + + signal_mouseReleased = QtCore.Signal() + signal_keyPressed = QtCore.Signal() + + def __init__(self, parent=None): + super(UVItemList, self).__init__() + + self.layout = QtWidgets.QHBoxLayout(self) + if parent: + parent.addWidget(self) + + self.treeModel = QtGui.QStandardItemModel() + self.rootNode = self.treeModel.invisibleRootItem() + + self.itemList = [] + + self.setModel(self.treeModel) + self.setHeaderHidden(True) + self.setAnimated(True) + self.setIndentation(style.scale(8)) + self.setSelectionMode(self.ExtendedSelection) + + def mouseReleaseEvent(self, event): + super(UVItemList, self).mouseReleaseEvent(event) + self.expandSelection() + self.signal_mouseReleased.emit() + + def keyPressEvent(self, event): + super(UVItemList, self).keyPressEvent(event) + self.signal_keyPressed.emit() + + def getSelection(self): + selectedItems = self.selectedIndexes() + selectedCurves = [] + for i in selectedItems: + model = i.model() + item = model.itemFromIndex(i) + selectedCurves.append(item.curveName) + return selectedCurves + + def getItemList(self): + return self.itemList + + def expandSelection(self): + indexes = self.selectedIndexes() + for index in indexes: + model = index.model() + item = model.itemFromIndex(index) + if item.hasChildren(): + for row in range(item.rowCount()): + self.selectionModel().select( + self.treeModel.index(row, 0, index), + self.selectionModel().Select + ) + + def selectItems(self, items): + if not items: + return + if not isinstance(items, list): + items = [items] + for item in items: + index = item.index() + self.selectionModel().select( + index, + self.selectionModel().Toggle + ) + + def clearItemList(self): + self.rootNode.removeRows(0, self.treeModel.rowCount()) + + def updateItemList(self, inputDict): + self.itemList *= 0 + self.clearItemList() + for key in inputDict: + parent = mc.listRelatives(key, p=1, pa=1) + rootItem = UVStandardItem(crvName=key, grpName=parent[0]) + self.itemList.append(rootItem) + self.rootNode.appendRow(rootItem) + if inputDict[key]: + rootItem.setBold() + for item in inputDict[key]: + if (mc.attributeQuery('gsmessage', n=item, ex=1) and + mc.connectionInfo(item + '.gsmessage', isSource=1)): + continue + parent = mc.listRelatives(item, p=1, pa=1) + subItem = UVStandardItem(crvName=item, grpName=parent[0]) + self.itemList.append(subItem) + rootItem.appendRow(subItem) + self.expandAll() + self.selectAll() diff --git a/Scripts/Modeling/Edit/gs_curvetools/uv_editor.py b/Scripts/Modeling/Edit/gs_curvetools/uv_editor.py new file mode 100644 index 0000000..58f9029 --- /dev/null +++ b/Scripts/Modeling/Edit/gs_curvetools/uv_editor.py @@ -0,0 +1,1336 @@ +""" + +License: +This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi) +and can not be copied or distributed without his written permission. + +GS CurveTools v1.3.1 Studio +Copyright 2023, George Sladkovsky (Yehor Sladkovskyi) +All Rights Reserved + +Autodesk Maya is a property of Autodesk, Inc. + +Social Media and Contact Links: + +Discord Server: https://discord.gg/f4DH6HQ +Online Store: https://sladkovsky3d.artstation.com/store +Online Documentation: https://gs-curvetools.readthedocs.io/ +Twitch Channel: https://www.twitch.tv/videonomad +YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky +ArtStation Portfolio: https://www.artstation.com/sladkovsky3d +Contact Email: george.sladkovsky@gmail.com + +""" + +import random +from functools import partial as pa + +import shiboken2 +from PySide2 import QtCore, QtGui, QtWidgets +from PySide2.QtCore import Qt + +from .utils import gs_math as mt +from .utils import utils +from .utils.wrap import WIDGETS + +# Buttons +LMB = Qt.LeftButton +RMB = Qt.RightButton +MMB = Qt.MiddleButton +ALT = Qt.AltModifier +CTRL = Qt.ControlModifier +SHIFT = Qt.ShiftModifier +NO_MOD = Qt.NoModifier +SCENE_W = 2000 +SCENE_H = 2000 + +# Texture cache +CACHE = {} + + +class Editor(QtWidgets.QGraphicsView): + """ + UV Editor View Main Widget + + """ + + signal_mousePress = QtCore.Signal(object) + signal_mouseMove = QtCore.Signal(object) + signal_mouseRelease = QtCore.Signal(object) + signal_keyPress = QtCore.Signal(object, object) + signal_functionKeyPress = QtCore.Signal(object) + signal_uvDrawEnd = QtCore.Signal() + signal_zoomChangeEvent = QtCore.Signal() + signal_forcedTextureMapUpdate = QtCore.Signal() + + def __init__(self, parent=None): + super(Editor, self).__init__(parent) + self.currentAction = 'NONE' + self.controllerMode = 'SELECT' + self.scaleMode = 'H' + self.initialMousePos = QtCore.QPoint(0, 0) + self.UVDict = dict() + self.textureItem = None + self.diffusePath = '' + self.alphaPath = '' + self.storedAlpha = None + + def init(self): + # Init scene + self.mainScene = EditorScene(self) + self.mainScene.setSceneRect(-SCENE_H / 2, -SCENE_W / 2, SCENE_H, SCENE_W) + self.setScene(self.mainScene) + + # Set initial Flags + self.setRenderHint(QtGui.QPainter.Antialiasing, True) + self.setRenderHint(QtGui.QPainter.TextAntialiasing, True) + self.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True) + self.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True) + self.setViewportUpdateMode(self.SmartViewportUpdate) + self.setResizeAnchor(self.AnchorViewCenter) + self.setTransformationAnchor(self.AnchorUnderMouse) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + # Initial scaling and centering + self.centerOn(50, -50) + self.scale(8, 8) + self.focusView() + + # Selection Rect Initialization + self.selectionRect = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self) + + # Mouse Line Init + self.mouseLine = MouseLine() + self.mousePoints = [0, 0, 0, 0] + self.mainScene.addItem(self.mouseLine) + + # Custom mouse cursor + self.customCursor = Cursor() + self.customCursor.sizeVerticalCursor() + self.mainScene.addItem(self.customCursor) + + # Rotation Circle + self.rotationCircle = RotationCircle() + self.mainScene.addItem(self.rotationCircle) + + # Initialize mouse variables + self.previousMouseDelta = 0 + self.zoomDirection = 0 + + # Draw Origin Dot + originBrush = QtGui.QBrush() + originBrush.setStyle(Qt.SolidPattern) + originBrush.setColor(QtGui.QColor(255, 0, 0)) + originPen = QtGui.QPen() + originPen.setWidth(0) + self.originDot = self.mainScene.addEllipse(-5, -5, 10, 10, pen=originPen, brush=originBrush) + self.originDot.setFlag(self.originDot.ItemIgnoresTransformations, True) + + def changeColor(self, + colorBG=(36, 36, 36), + colorGrid=(50, 50, 50), + colorFrame=(160, 75, 75), + uvColorBG=(96, 100, 160), + uvColorSelected=(255, 255, 255), + uvColorDeselected=(128, 128, 128)): + if self.mainScene: + self.mainScene.colorBG = (list(colorBG) + [255]) + self.mainScene.colorGrid = (list(colorGrid) + [255]) + self.mainScene.colorFrame = (list(colorFrame) + [128]) + UVItem.bgColor = QtGui.QColor(*(list(uvColorBG) + [1])) + UVItem.selectedColor = QtGui.QColor(*(list(uvColorSelected) + [255])) + UVItem.deselectedColor = QtGui.QColor(*(list(uvColorDeselected) + [255])) + for uv in self.getAllUVs(): + uv.bgColor = QtGui.QColor(*(list(uvColorBG) + [1])) + uv.selectedColor = QtGui.QColor(*(list(uvColorSelected) + [255])) + uv.deselectedColor = QtGui.QColor(*(list(uvColorDeselected) + [255])) + self.mainScene.update() + + def getCurrentTexture(self): + return self.textureItem + + def toggleAlpha(self, enable): + if self.textureItem: + if enable: + self.textureItem.enableAlpha() + else: + self.textureItem.disableAlpha() + + def setTexture(self, diffuse, alpha=None, coverage=(1.0, 1.0), translation=(0, 0)): + self.removeTexture() + self.diffusePath = diffuse + self.alphaPath = alpha + + self.textureItem = self.createTextureItem(self.diffusePath, self.alphaPath, coverage, translation) + + if not self.textureItem: + return 'NoTexture' + if not (self.textureItem.customPixmap.width() > 0 and + self.textureItem.customPixmap.height() > 0): + return 'ZeroTexture' + + self.setTextureItem(self.textureItem) + + return 'Success' + + def createTextureItem(self, diffusePath, alphaPath, coverage, translation): + # Using cached TextureItem (or creating new one) + diffuseTime = QtCore.QFileInfo(diffusePath).lastModified() + alphaTime = None + if alphaPath: + alphaTime = QtCore.QFileInfo(alphaPath).lastModified() + shouldUpdate = True + if diffusePath in CACHE and shiboken2.isValid(CACHE[diffusePath][0]): # pylint: disable=maybe-no-member + shouldUpdate = False + # Update if Diffuse texture has different timestamp + if CACHE[diffusePath][1] != diffuseTime: + shouldUpdate = True + # Update if Alpha was added + if not CACHE[diffusePath][2] and alphaTime: + shouldUpdate = True + # Update if Alpha was removed + if not alphaTime and CACHE[diffusePath][2]: + shouldUpdate = True + # Update if Alpha has different timestamp + if CACHE[diffusePath][2] and CACHE[diffusePath][2] != alphaTime: + shouldUpdate = True + # Update if coverage or translation changed + if CACHE[diffusePath][0].customCoverage != coverage or CACHE[diffusePath][0].customTranslation != translation: + shouldUpdate = True + if shouldUpdate: + textureItem = TextureItem(diffusePath, alphaPath, coverage, translation) + CACHE[diffusePath] = (textureItem, diffuseTime, alphaTime) + if 'UVEditorTransparencyToggle' in WIDGETS: + if WIDGETS['UVEditorTransparencyToggle'].isChecked(): + textureItem.enableAlpha() + else: + textureItem.disableAlpha() + else: + textureItem = CACHE[diffusePath][0] + return textureItem + + def setTextureItem(self, textureItem): + if textureItem: + textureItem.setScale( + 1.0 / (max(textureItem.customPixmap.width(), + textureItem.customPixmap.height())) * 100) + textureItem.setOffset(0, -100.0 / textureItem.scale()) + + # Avoid multiple connections when setting textures + try: + self.signal_zoomChangeEvent.disconnect() + except Exception: + pass + try: + self.signal_forcedTextureMapUpdate.disconnect() + except Exception: + pass + + # Connecting signals + self.signal_zoomChangeEvent.connect(textureItem.updatePixmap) + self.signal_forcedTextureMapUpdate.connect(pa(textureItem.updatePixmap, True)) + + self.mainScene.addItem(textureItem) + textureItem.updatePixmap(forceUpdate=True) + + return 'Success' + + def removeTexture(self): + for item in self.items(): + if isinstance(item, TextureItem): + self.mainScene.removeItem(item) + + def createUV(self, name): + if name in self.UVDict: + try: + self.mainScene.removeItem(self.UVDict[name]) + except BaseException: + pass + self.UVDict[name] = UVItem(name) + self.mainScene.addItem(self.UVDict[name]) + return self.UVDict[name] + + def purgeUVs(self): + for uv in self.UVDict: + self.mainScene.removeItem(self.UVDict[uv]) + self.UVDict.clear() + + def getUVs(self): + returnDict = dict() + selection = [i.name for i in self.getAllUVs(selected=True)] + for uv in self.UVDict: + if uv in selection: + returnDict[uv] = self.UVDict[uv].getAttrs() + return returnDict + + def wheelEvent(self, event): + # Wheel Zoom + self.setTransformationAnchor(self.AnchorUnderMouse) + self.currentAction = 'ZOOM' + zoomIncrement = 0.1 + if event.delta() > 0: + if self.transform().m11() >= 500: + zoomIncrement = 0 + zoom = 1 + zoomIncrement + else: + if self.transform().m11() <= 1: + zoomIncrement = 0 + zoom = 1 - zoomIncrement + self.scale(zoom, zoom) + self.currentAction = 'NONE' + self.signal_forcedTextureMapUpdate.emit() + + def initializeMouseLine(self): + if self.currentAction in ['SCALE', 'ROTATE']: + self.mouseLine.setVisible(True) + self.mouseLine.setPoints(*self.mousePoints) + else: + self.mouseLine.setVisible(False) + + def initializeRotationCircle(self): + if self.currentAction == 'ROTATE': + self.rotationCircle.setVisible(True) + self.rotationCircle.rotationRect.moveCenter( + self.rotationCircle.mapFromScene(self.mousePoints[0], self.mousePoints[1]) + ) + else: + self.rotationCircle.setVisible(False) + + def mousePressEvent(self, event): + # TODO: Improve selection mode. Should select only one card if clicked, just like in Move mode. + self.signal_mousePress.emit(event) + self.setTransformationAnchor(self.AnchorUnderMouse) + button = event.button() + mod = event.modifiers() + + # Switching between controller flags + items = self.getAllUVs() + if self.controllerMode in ['SELECT', 'ROTATE', 'SCALE']: + for item in items: + item.setFlag(item.ItemIsMovable, False) + else: + for item in items: + item.setFlag(item.ItemIsMovable, True) + + # Initializing controllers: + + # Scale controller + if self.controllerMode == 'SCALE' and button == LMB: + self.currentAction = 'SCALE' + + items = self.getAllUVs(selected=True) + if items: + lastItem = items[-1] + + self.setCursor(Qt.BlankCursor) + self.customCursor.setPos(self.mapToScene(event.pos())) + self.customCursor.setVisible(True) + + if self.scaleMode == 'V': + self.customCursor.setRotation(lastItem.rotation()) + A = lastItem.mapToScene(lastItem.mainRect.bottomLeft()) + B = lastItem.mapToScene(lastItem.mainRect.bottomRight()) + else: + self.customCursor.setRotation(lastItem.rotation() + 90) + A = lastItem.mapToScene(lastItem.yAxisLine.p1().toPoint()) + B = lastItem.mapToScene(lastItem.yAxisLine.p2().toPoint()) + + P = self.mapToScene(event.pos()) + finalPoint = mt.projectPoint(A, B, P) + self.initLength = QtCore.QLineF(P, finalPoint).length() + + scenePos = self.mapToScene(event.pos()) + self.mousePoints = [ + finalPoint.x(), + finalPoint.y(), + scenePos.x(), + scenePos.y() + ] + self.initializeMouseLine() + for item in items: + item.initXScale() + item.initYScale() + + self.initialMousePos = event.pos() + self.setInteractive(False) + + # Rotation controller + elif self.controllerMode == 'ROTATE' and button == LMB: + self.currentAction = 'ROTATE' + + self.setCursor(Qt.BlankCursor) + self.customCursor.setPos(self.mapToScene(event.pos())) + self.customCursor.setVisible(True) + + items = self.getAllUVs(selected=True) + if items: + scenePos = self.mapToScene(event.pos()) + self.mousePoints = [ + items[-1].rotatePivot.x(), + items[-1].rotatePivot.y(), + scenePos.x(), + scenePos.y() + ] + self.initializeMouseLine() + self.initializeRotationCircle() + self.initAngle = self.mouseLine.mouseLine.angleTo(self.scene().xLine) + self.customCursor.setRotation(self.initAngle) + self.setInteractive(False) + + # Move controller + elif self.controllerMode == 'MOVE' and button == LMB: + self.currentAction = 'MOVE' + self.setCursor(Qt.SizeAllCursor) + + # Draw UV controller + elif self.controllerMode == 'DRAW' and button == LMB: + self.setCursor(Qt.CrossCursor) + self.selectionRect.setStyle(QtWidgets.QStyleFactory.create('Fusion')) + self.currentAction = 'DRAW' + self._startSelection(event.pos()) + self.setInteractive(False) + + # Zoom using drag + elif button == RMB and mod == ALT: + self.currentAction = 'ZOOM' + self.initialMousePos = event.pos() + self.zoomInitialPos = event.pos() + self.setInteractive(False) + + # Panning the view + elif button == MMB: + self.currentAction = 'PAN' + self.previousPosition = event.pos() + self.setCursor(Qt.ClosedHandCursor) + self.setInteractive(False) + + # Selection Rect + elif button == LMB and (mod == CTRL or mod == NO_MOD): + self.currentAction = 'SELECT' + self._startSelection(event.pos()) + self.setInteractive(False) + + # Click selection + elif button == LMB and (mod == CTRL and mod == CTRL | SHIFT): + self.currentAction = 'SELECT_ADD' + self._startSelection(event.pos()) + self.setInteractive(False) + + # Click remove selection + elif button == LMB and mod == CTRL: + self.currentAction = 'SELECT_REMOVE' + self._startSelection(event.pos()) + self.setInteractive(False) + + # Click toggle selection + elif button == LMB and mod == SHIFT: + self.currentAction = 'SELECT_TOGGLE' + self._startSelection(event.pos()) + self.setInteractive(False) + + # Default action + else: + self.setCursor(Qt.ArrowCursor) + self.currentAction = 'NONE' + + super(Editor, self).mousePressEvent(event) + + def mouseMoveEvent(self, event): + if not self.isActiveWindow(): + self.activateWindow() + if not self.hasFocus(): + self.setFocus() + + self.initializeMouseLine() + self.initializeRotationCircle() + + # Move controller drag + if self.currentAction == 'MOVE': + pass + + # Scale controller drag + elif self.currentAction == 'SCALE': + # Updating the mouse line position + self.scenePos = self.mapToScene(event.pos()) + + # Calculating delta scale + delta = self.initialMousePos - event.pos() + + # Applying scale to items + items = self.getAllUVs(selected=True) + if items: + self.customCursor.setPos(self.mapToScene(event.pos())) + + lastItem = items[-1] + if self.scaleMode == 'V': + A = lastItem.mapToScene(lastItem.mainRect.bottomLeft()) + B = lastItem.mapToScene(lastItem.mainRect.bottomRight()) + else: + A = lastItem.mapToScene(lastItem.yAxisLine.p1().toPoint()) + B = lastItem.mapToScene(lastItem.yAxisLine.p2().toPoint()) + + P = self.mapToScene(event.pos()) + finalPoint = mt.projectPoint(A, B, P) + length = QtCore.QLineF(P, finalPoint).length() + if self.scaleMode == 'V': + for item in items: + item.changeYScale(length - self.initLength) + else: + for item in items: + item.changeXScale(length - self.initLength) + + self.mousePoints = [ + finalPoint.x(), + finalPoint.y(), + self.scenePos.x(), + self.scenePos.y() + ] + + # Rotate controller drag + elif self.currentAction == 'ROTATE': + self.scenePos = self.mapToScene(event.pos()) + items = self.getAllUVs(selected=True) + + if items: + self.customCursor.setPos(self.mapToScene(event.pos())) + self.mousePoints = [ + items[-1].rotatePivot.x(), + items[-1].rotatePivot.y(), + self.scenePos.x(), + self.scenePos.y() + ] + self.initializeMouseLine() + + currentAngle = self.mouseLine.mouseLine.angleTo(self.scene().xLine) + for item in items: + angleDelta = mt.angleDiff(currentAngle, self.initAngle) + newRotation = item.initRotation + angleDelta + self.customCursor.setRotation(currentAngle) + if event.modifiers() == SHIFT: + newRotation = int(newRotation) + if not abs(newRotation) % 15: + item.setRotation(newRotation) + else: + item.setRotation(newRotation) + + # UV drawing update + elif self.currentAction == 'DRAW': + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + + # Zooming using drag + elif self.currentAction == 'ZOOM': + self.setCursor(Qt.SizeHorCursor) + + delta = self.zoomInitialPos.x() - event.pos().x() + if self.previousMouseDelta > delta: + self.zoomDirection = 1 + elif self.previousMouseDelta < delta: + self.zoomDirection = -1 + else: + if self.zoomDirection == -1: + self.zoomDirection = -1 + else: + self.zoomDirection = 1 + self.previousMouseDelta = delta + + # Zoom factor + zoomAmount = 0.015 + if self.zoomDirection == 1: + if self.transform().m11() >= 500: + zoomAmount = 0 + zoom = 1 + zoomAmount + else: + if self.transform().m11() <= 1: + zoomAmount = 0 + zoom = 1 - zoomAmount + + # Zooming and centering on clicked point + self.setTransformationAnchor(self.AnchorViewCenter) + initPoint = self.mapToScene(self.initialMousePos) + self.scale(zoom, zoom) + afterPoint = self.mapToScene(self.initialMousePos) + pointDelta = afterPoint - initPoint + + self.setTransformationAnchor(self.NoAnchor) + self.translate(pointDelta.x(), pointDelta.y()) + + self.signal_zoomChangeEvent.emit() + + # Panning the view + elif self.currentAction == 'PAN': + delta = self.previousPosition - event.pos() + self.previousPosition = event.pos() + self.translate(self.pos().x() + delta.x(), + self.pos().y() + delta.y()) + + # Creating selection rectangle + elif self.currentAction in ['SELECT', 'SELECT_ADD', 'SELECT_REMOVE', 'SELECT_TOGGLE']: + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + + # Default Action + else: + self.currentAction = 'NONE' + + if self.controllerMode == 'DRAW': + self.setCursor(Qt.CrossCursor) + + super(Editor, self).mouseMoveEvent(event) + + # Emit move signal + if self.currentAction in ['MOVE', 'ROTATE', 'SCALE']: + self.signal_mouseMove.emit(event) + + def mouseReleaseEvent(self, event): + + self.customCursor.setVisible(False) + + # Zooming + if self.currentAction == 'ZOOM': + # Reset the zoom + self.setCursor(Qt.ArrowCursor) + self.zoomDirection = 0 + self.signal_forcedTextureMapUpdate.emit() + + # Panning the view + elif self.currentAction == 'PAN': + # Reset the panning + self.setCursor(Qt.ArrowCursor) + + # UV Drawing + elif self.currentAction == 'DRAW': + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + painterPath = self._releaseSelection() + origGeo = self.selectionRect.geometry() + geo = self.mapToScene(origGeo).boundingRect() + self.drawUV(geo) + self.selectionRect.setStyle(QtWidgets.QStyleFactory.create('adskdarkflatui')) + + # Click selection + elif self.currentAction == 'SELECT': + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + painterPath = self._releaseSelection() + self.scene().setSelectionArea(painterPath) + + # Click add selection + elif self.currentAction == 'SELECT_ADD': + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + painterPath = self._releaseSelection() + for item in self.scene().items(painterPath): + item.setSelected(True) + + # Click remove selection + elif self.currentAction == 'SELECT_REMOVE': + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + painterPath = self._releaseSelection() + for item in self.scene().items(painterPath): + item.setSelected(False) + + # Click toggle selection + elif self.currentAction == 'SELECT_TOGGLE': + self.selectionRect.setGeometry( + QtCore.QRect(self.selectionOrigin, event.pos()).normalized() + ) + painterPath = self._releaseSelection() + for item in self.scene().items(painterPath): + if item.isSelected(): + item.setSelected(False) + else: + item.setSelected(True) + + self.initAngle = self.mouseLine.mouseLine.angleTo(self.scene().xLine) + for item in self.getAllUVs(selected=True): + item.initRotation = item.rotation() + + self.mouseLine.setVisible(False) + self.rotationCircle.setVisible(False) + + self.currentAction = 'NONE' + self.setInteractive(True) + self.setCursor(Qt.ArrowCursor) + + self.scene().update() + self.signal_mouseRelease.emit(event) + + super(Editor, self).mouseReleaseEvent(event) + + def keyPressEvent(self, event): + # Filter all key press events + key = event.key() + mod = event.modifiers() + if event.type() == QtCore.QEvent.KeyPress: + self.setCursor(Qt.ArrowCursor) + if key == Qt.Key_F: + self.focusView() + self.signal_forcedTextureMapUpdate.emit() + elif key == Qt.Key_I: + self.signal_functionKeyPress.emit('I') + elif key == Qt.Key_H: + self.signal_functionKeyPress.emit('H') + elif key == Qt.Key_A: + self.signal_functionKeyPress.emit('A') + elif key == Qt.Key_V: + self.signal_functionKeyPress.emit('V') + elif key == Qt.Key_X: + self.signal_functionKeyPress.emit('X') + elif key == Qt.Key_O: + self.signal_functionKeyPress.emit('O') + elif key == Qt.Key_S: + self.signal_functionKeyPress.emit('S') + elif key == Qt.Key_Q: + self.controllerMode = 'SELECT' + elif key == Qt.Key_W: + self.controllerMode = 'MOVE' + elif key == Qt.Key_E: + self.controllerMode = 'ROTATE' + elif key == Qt.Key_R: + if self.controllerMode == 'SCALE': + self.scaleMode = 'H' if self.scaleMode == 'V' else 'V' + self.controllerMode = 'SCALE' + elif key == Qt.Key_D: + self.controllerMode = 'DRAW' + elif key == Qt.Key_Z and mod == CTRL: + super(Editor, self).keyPressEvent(event) + elif key == Qt.Key_Y and mod == CTRL: + super(Editor, self).keyPressEvent(event) + for item in self.getAllUVs(selected=True): + item.update() + elif mod == CTRL: + pass + else: + super(Editor, self).keyPressEvent(event) + self.signal_keyPress.emit(self.controllerMode, self.scaleMode) + + def controllerModeChange(self, mode, direction=None): + self.controllerMode = mode + if direction: + self.scaleMode = direction + for item in self.getAllUVs(selected=True): + item.update() + + def _startSelection(self, pos): + self.selectionOrigin = pos + self.selectionRect.setGeometry(QtCore.QRect(self.selectionOrigin, QtCore.QSize())) + self.selectionRect.show() + + def _releaseSelection(self): + painterPath = QtGui.QPainterPath() + rect = self.mapToScene(self.selectionRect.geometry()) + painterPath.addPolygon(rect) + self.selectionRect.hide() + return painterPath + + def focusView(self): + items = self.getAllUVs() + selectedItems = self.getAllUVs(selected=True) + finalRect = None + + if selectedItems: + finalRect = selectedItems[0].sceneBoundingRect() + for i in range(1, len(selectedItems)): + finalRect |= selectedItems[i].sceneBoundingRect() + finalRect.adjust(-2, -2, 2, 2) + self.fitInView(finalRect, Qt.KeepAspectRatio) + elif items: + finalRect = items[0].sceneBoundingRect() + for i in range(1, len(items)): + finalRect |= items[i].sceneBoundingRect() + if self.textureItem: + finalRect |= self.textureItem.sceneBoundingRect() + else: + finalRect |= QtCore.QRectF(0, 0, 100, -100) + finalRect.adjust(-2, -2, 2, 2) + self.fitInView(finalRect, Qt.KeepAspectRatio) + + self.signal_zoomChangeEvent.emit() + + def randomizeUVs(self, mod): + selectedItems = set(self.getAllUVs(selected=True)) + finalList = [] + while selectedItems: + ele = selectedItems.pop() + same = set() + same.add(ele) + for item in selectedItems: + if ele.getAttrs() == item.getAttrs(): + same.add(item) + selectedItems = selectedItems - same + finalList.append(list(same)) + + groups = [(len(x), x[0].getAttrs()) for x in finalList] + flattenedList = [element for sublist in finalList for element in sublist] + random.shuffle(flattenedList) + if mod: + for i in range(len(groups)): + flattenedList[0].moveUV( + x=groups[i][1]['moveU'], + y=groups[i][1]['moveV'], + rot=groups[i][1]['rotateUV'], + sx=groups[i][1]['scaleU'], + sy=groups[i][1]['scaleV'] + ) + flattenedList.pop(0) + for item in flattenedList: + randI = random.randint(0, len(groups) - 1) + item.moveUV( + x=groups[randI][1]['moveU'], + y=groups[randI][1]['moveV'], + rot=groups[randI][1]['rotateUV'], + sx=groups[randI][1]['scaleU'], + sy=groups[randI][1]['scaleV'] + ) + else: + for group in groups: + applied = [] + for i in range(group[0]): + flattenedList[i].moveUV( + x=group[1]['moveU'], + y=group[1]['moveV'], + rot=group[1]['rotateUV'], + sx=group[1]['scaleU'], + sy=group[1]['scaleV'] + ) + applied.append(flattenedList[i]) + flattenedList = list(set(flattenedList) - set(applied)) + + def verticalFlipUV(self): + items = self.getAllUVs(selected=True) + for item in items: + pre = item.mapToScene(item.mainRect.topLeft()) + item.setRotation((item.rotation() + 180) % 360) + post = item.mapToScene(item.mainRect.bottomRight()) + delta = post - pre + item.setPos(item.pos() - delta) + item.initRotation = item.rotation() + + def resetUV(self): + for item in self.getAllUVs(selected=True): + item.resetRect() + + def drawUV(self, rect): + x = rect.center().x() - 50 + y = rect.bottom() + sx = (rect.right() - rect.left()) / 100.0 + sy = -(rect.top() - rect.bottom()) / 100.0 + if sx > 0.02 or sy > 0.02: + for item in self.getAllUVs(selected=True): + if item.isVisible(): + item.setRotation(0) + item.setPos(QtCore.QPointF(x, y)) + item.setXScale(sx) + item.setYScale(sy) + self.signal_uvDrawEnd.emit() + + def getAllUVs(self, selected=False): + # type: (bool) -> list[UVItem] + UVs = [] + for item in self.scene().items(): + if isinstance(item, UVItem): + if selected: + if item.isSelected(): + UVs.append(item) + else: + continue + else: + UVs.append(item) + return UVs + + +class EditorScene(QtWidgets.QGraphicsScene): + """ + UV Editor Scene + + """ + + def __init__(self, parent=None): + super(EditorScene, self).__init__(parent) + self.colorBG = (36, 36, 36, 255) + self.colorGrid = (50, 50, 50, 128) + self.colorFrame = (160, 75, 75, 255) + + def drawBackground(self, painter, rect): + # Draw background + self.gridSpacing = 10 + + self._brush = QtGui.QBrush() + self._brush.setStyle(Qt.SolidPattern) + self._brush.setColor(QtGui.QColor(*self.colorBG)) + + leftLine = rect.left() - rect.left() % self.gridSpacing + topLine = rect.top() - rect.top() % self.gridSpacing + + painter.fillRect(rect, self._brush) + + lines = [] + i = int(leftLine) + while i < int(rect.right()): + lines.append(QtCore.QLineF(i, rect.top(), i, rect.bottom())) + i += self.gridSpacing + + u = int(topLine) + while u < int(rect.bottom()): + lines.append(QtCore.QLineF(rect.left(), u, rect.right(), u)) + u += self.gridSpacing + + # Draw Grid Lines + gridPen = QtGui.QPen() + gridPen.setColor(QtGui.QColor(*self.colorGrid)) + gridPen.setWidth(0) + painter.setPen(gridPen) + painter.drawLines(lines) + + # Draw Axis Lines + axisPen = QtGui.QPen() + axisPen.setColor(QtGui.QColor(*self.colorFrame)) + axisPen.setWidth(0) + self.xLine = QtCore.QLineF(rect.left(), 0, rect.right(), 0) + self.yLine = QtCore.QLineF(0, rect.top(), 0, rect.bottom()) + xEndLine = QtCore.QLineF(rect.left(), -100, rect.right(), -100) + yEndLine = QtCore.QLineF(100, rect.top(), 100, rect.bottom()) + painter.setPen(axisPen) + painter.drawLines([self.xLine, self.yLine, xEndLine, yEndLine]) + + +class TextureItem(QtWidgets.QGraphicsPixmapItem): + """ + Creates a texture item for background use in UV Editor + """ + + def __init__(self, pixmapPath, alphaPath=None, coverage=(1.0, 1.0), offset=(0, 0)): + super(TextureItem, self).__init__() + self.UPDATE = True + self.pixmapPath = pixmapPath + self.alphaPath = alphaPath + self.storedAlpha = None + self.timer = utils.Timer() + self.origPixmap = QtGui.QPixmap() + self.customPixmap = QtGui.QPixmap() + self.customCoverage = coverage + self.coverageW = self.customCoverage[0] + self.coverageH = self.customCoverage[1] + self.customTranslation = offset + self.translationX = self.customTranslation[0] + self.translationY = self.customTranslation[1] + self.createPixmap() + + def createPixmap(self): + self.image = QtGui.QImage(self.pixmapPath) + if self.alphaPath: + alphaChannel = QtGui.QImage(self.alphaPath) + self.storedAlpha = self.alphaPath + if self.pixmapPath == self.alphaPath: + alphaChannel = QtGui.QImage(self.alphaPath).convertToFormat(self.image.Format_Alpha8) + if WIDGETS['UVEditorAlphaOnlyToggle'].isChecked(): + self.image = self.image.alphaChannel().convertToFormat(self.image.Format_RGB888) + else: + self.image = self.image.convertToFormat(self.image.Format_RGBA8888) + else: + alphaChannel = QtGui.QImage(self.alphaPath) + if WIDGETS['UVEditorAlphaOnlyToggle'].isChecked(): + self.image = alphaChannel.convertToFormat(self.image.Format_RGB888) + self.image.setAlphaChannel(alphaChannel) + else: + self.image = self.image.convertToFormat(self.image.Format_RGBX8888) + + self.origPixmap.convertFromImage(self.image) + self.setTransformationMode(Qt.SmoothTransformation) + self.customPixmap.convertFromImage(self.image) + self.setPixmap(self.customPixmap) + self.updatePixmap(forceUpdate=True) + + def updatePixmap(self, forceUpdate=False): + timer = self.timer.increment(1.0 / 5.0) + if forceUpdate: + timer = True + if not timer: + return + self.customPixmap = self.origPixmap + scene = self.scene() + if self.customPixmap.width() and self.customPixmap.height(): + if scene: + sceneRect = self.boundingRect() + l = scene.parent().mapToGlobal(self.mapToScene(sceneRect.topLeft()).toPoint()) + r = scene.parent().mapToGlobal(self.mapToScene(sceneRect.topRight()).toPoint()) + w_o = (r - l) * scene.parent().transform().m11() + x = w_o.x() + if x <= 0: + x = 100 + self.mult = self.customPixmap.height() / float(x) + else: + self.mult = 1.0 + if self.mult < 1: + self.mult = 1.0 + scale = 1.0 / (max(self.customPixmap.width(), + self.customPixmap.height())) * 100 * self.mult + self.setScale(scale) + resMax = max(self.customPixmap.width(), self.customPixmap.height()) + # resMin = min(self.customPixmap.width(), self.customPixmap.height()) + aspectWidth = (self.customPixmap.width() / float(resMax)) * (1.0 / self.coverageW) + aspectHeight = (self.customPixmap.height() / float(resMax)) * (1.0 / self.coverageH) + # print(aspectWidth, aspectHeight) + pixWidth = self.customPixmap.width() / float(self.mult * aspectWidth) + pixHeight = self.customPixmap.height() / float(self.mult * aspectHeight) + # self.customPixmap = self.customPixmap.scaledToHeight(pixHeight, Qt.SmoothTransformation) + self.customPixmap = self.customPixmap.scaled( + QtCore.QSize(pixWidth, pixHeight), + Qt.AspectRatioMode.IgnoreAspectRatio, + Qt.SmoothTransformation) + self.setOffset(self.translationX * 100.0 / self.scale(), + (-100.0 * self.coverageH + self.translationY * -100.0) / self.scale()) + self.setPixmap(self.customPixmap) + + def enableAlpha(self): + if self.storedAlpha: + self.alphaPath = self.storedAlpha + self.createPixmap() + self.updatePixmap(True) + + def disableAlpha(self): + if self.alphaPath: + self.storedAlpha = self.alphaPath + self.alphaPath = '' + self.createPixmap() + self.updatePixmap(True) + + +class UVItem(QtWidgets.QGraphicsItem): + """ + UV Rectangle and controls implementation + + """ + bgColor = QtGui.QColor(96, 100, 160, 1) + selectedColor = QtGui.QColor(255, 255, 255, 255) + deselectedColor = QtGui.QColor(128, 128, 128, 255) + + def __init__(self, name=None, x1=0, y1=-100, x2=100, y2=0): + super(UVItem, self).__init__() + + self.name = name + self.flip = False + self.setFlag(self.ItemIsMovable, True) + self.setFlag(self.ItemIsSelectable, True) + self.setZValue(1) + + self.initRotation = self.rotation() + self.initPos = self.pos() + + self.mainRect = QtCore.QRectF(QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)) + + self.xAxisLine = QtCore.QLineF(0, -50, 100, -50) + self.yAxisLine = QtCore.QLineF(50, 0, 50, -100) + + self.rotatePivot = QtCore.QPointF(50, -50) + self.rootPoint = QtCore.QRectF(0, 0, 0, 1) + self.flipIndication = QtCore.QRectF(0, 0, 0, 1) + + self.setTransformOriginPoint(50, -50) + + self.initXScale() + self.initYScale() + + def paint(self, painter, *_): + # Main Rect Painting + pen = QtGui.QPen() + if self.isSelected(): + pen.setColor(self.selectedColor) + else: + pen.setColor(self.deselectedColor) + pen.setWidth(2) + pen.setCosmetic(True) + painter.setPen(pen) + brush = QtGui.QBrush() + brush.setStyle(Qt.SolidPattern) + brush.setColor(self.bgColor) + painter.setBrush(brush) + painter.drawRect(self.mainRect) + + if self.isSelected(): + # Root Point Painting + self.rootPoint.setWidth(min(5 * self.mainRect.width() / 20, 5)) + self.rootPoint.moveCenter(QtCore.QPointF(50, -.5)) + painter.drawRect(self.rootPoint) + + # Axis Lines Painting + pen.setStyle(Qt.DashLine) + pen.setWidthF(0) + painter.setPen(pen) + self.updateAxisLines() + if (self.scene().parent().scaleMode == 'V' and + self.scene().parent().controllerMode == 'SCALE'): + painter.drawLine(self.xAxisLine) + elif (self.scene().parent().scaleMode == 'H' and + self.scene().parent().controllerMode == 'SCALE'): + painter.drawLine(self.yAxisLine) + + # Flip indicator Painting + if self.flip: + brush = QtGui.QBrush() + brush.setStyle(Qt.SolidPattern) + brush.setColor(QtGui.QColor(0, 0, 255, 128)) + painter.setBrush(brush) + pen.setStyle(Qt.SolidLine) + pen.setWidthF(0) + painter.setPen(pen) + self.flipIndication.setWidth(.95) + self.flipIndication.setHeight(self.flipIndication.width()) + self.flipIndication.moveCenter(QtCore.QPointF(50, -.5)) + painter.drawEllipse(self.flipIndication) + + self.updatePivotPoint() + + def boundingRect(self): + rect = QtCore.QRectF(self.mainRect) + rect.adjust(-1, -1, 1, 1) + return rect + + def shape(self): + path = QtGui.QPainterPath() + rect = QtCore.QRectF(self.mainRect) + rect.adjust(-1, -1, 1, 1) + path.addRect(rect) + return path + + def updatePivotPoint(self): + self.rotatePivot = QtCore.QPointF(50, -50) + self.pos() + + def updateAxisLines(self): + self.xAxisLine.setLine( + self.mainRect.left(), self.mainRect.top() / 2, + self.mainRect.right(), self.mainRect.top() / 2 + ) + self.yAxisLine.setLine( + 50, 0, + 50, self.mainRect.top() + ) + + def initXScale(self): + self.initLeft = self.mainRect.left() + self.initRight = self.mainRect.right() + + def initYScale(self): + self.initTop = self.mainRect.top() + + def moveUV(self, x=None, y=None, rot=None, sx=None, sy=None): + """ Transform UV object directly from Maya """ + if x is not None and y is not None: + self.setPos(x * 100, -(y * 100)) + elif x is not None: + self.setPos(x * 100, self.pos().y()) + elif y is not None: + self.setPos(self.pos().x(), -(y * 100)) + if rot is not None: + self.setRotation(rot) + if sx is not None: + self.setXScale(sx) + if sy is not None: + self.setYScale(sy) + + def changeYScale(self, delta): + """ Set the scale using controllers """ + self.prepareGeometryChange() + newTop = self.initTop - delta + if newTop <= -1: + self.mainRect.setTop(self.initTop - delta) + self.mainRect = self.mainRect.normalized() + self.update() + + def setYScale(self, scale): + """ Set the scale directly from Maya """ + self.prepareGeometryChange() + top = round(100 * scale, 14) + self.mainRect.setTop(-top) + self.mainRect = self.mainRect.normalized() + self.update() + + def changeXScale(self, delta): + """ Set the scale using controllers """ + self.prepareGeometryChange() + self.mainRect.setLeft(self.initLeft - delta) + self.mainRect.setRight(self.initRight + delta) + self.mainRect = self.mainRect.normalized() + self.update() + + def setXScale(self, scale): + """ Set the scale directly from Maya """ + self.prepareGeometryChange() + sl = round((1 - scale) / 2 * 100, 14) + sr = round((1 - ((1 - scale) / 2)) * 100, 14) + self.mainRect.setLeft(sl) + self.mainRect.setRight(sr) + self.mainRect = self.mainRect.normalized() + self.update() + + def getAttrs(self): + attrs = dict() + attrs['moveU'] = self.pos().x() / 100 + attrs['moveV'] = self.pos().y() / 100 * -1 + attrs['rotateUV'] = self.rotation() * -1 + attrs['scaleU'] = round((100 - (100 - self.mainRect.right()) * 2) / 100, 14) + attrs['scaleV'] = self.mainRect.top() / 100 * -1 + return attrs + + def resetRect(self): + self.setPos(0, 0) + self.setRotation(0) + self.mainRect = QtCore.QRectF(QtCore.QPointF(0, -100), QtCore.QPointF(100, 0)) + self.update() + + +class MouseLine(QtWidgets.QGraphicsItem): + + def __init__(self): + super(MouseLine, self).__init__() + self.setVisible(False) + self.setZValue(100) + + # Mouse Line Init + self.mouseLine = QtCore.QLineF(0, 0, 0, 0) + + # Rotation Pivot Point + self.pivotPoint = QtCore.QRectF(0, 0, 20, 20) + + def setPoints(self, x1=0, y1=0, x2=0, y2=0): + scale = self.scene().parent().transform().m11() + self.prepareGeometryChange() + self.mouseLine.setLine(x1, y1, x2, y2) + self.pivotPoint.setWidth(2 / scale * 10) + self.pivotPoint.setHeight(2 / scale * 10) + self.pivotPoint.moveCenter(QtCore.QPointF(x1, y1)) + self.update() + + def paint(self, painter, *_): + pen = QtGui.QPen() + pen.setColor(QtGui.QColor(128, 0, 0, 128)) + pen.setCosmetic(True) + pen.setStyle(Qt.DashLine) + pen.setWidthF(3) + + painter.setPen(pen) + painter.drawLine(self.mouseLine) + + pen = QtGui.QPen() + pen.setColor(QtGui.QColor(255, 0, 0, 128)) + pen.setCosmetic(True) + pen.setWidthF(0) + + brush = QtGui.QBrush() + brush.setStyle(Qt.SolidPattern) + brush.setColor(QtGui.QColor(255, 0, 0, 128)) + painter.setPen(pen) + painter.setBrush(brush) + + painter.drawEllipse(self.pivotPoint) + + def boundingRect(self): + self.bRect = QtCore.QRectF(self.mouseLine.p1(), self.mouseLine.p2()).united(self.pivotPoint) + return self.bRect.normalized() + + def shape(self): + path = QtGui.QPainterPath() + path.addRect(self.bRect) + return path + + +class RotationCircle(QtWidgets.QGraphicsItem): + + def __init__(self): + super(RotationCircle, self).__init__() + self.setZValue(100) + self.setVisible(False) + + # Rect + self.rotationRect = QtCore.QRectF(0, 0, 20, 20) + + self.rotationRect.moveCenter(self.mapFromScene(QtCore.QPoint(0, 0))) + self.setTransformOriginPoint(self.rotationRect.center()) + + def paint(self, painter, *_): + scale = self.scene().parent().transform().m11() + pen = QtGui.QPen() + pen.setColor(QtGui.QColor(128, 0, 0, 128)) + pen.setCosmetic(True) + pen.setWidthF(3) + + painter.setPen(pen) + self.rotationRect.setWidth(20 / scale * 10) + self.rotationRect.setHeight(20 / scale * 10) + painter.drawEllipse(self.rotationRect) + + def boundingRect(self): + self.bRect = self.rotationRect.normalized() + return self.bRect + + def shape(self): + path = QtGui.QPainterPath() + path.addRect(self.bRect) + return path + + +class Cursor(QtWidgets.QGraphicsItem): + + def __init__(self): + super(Cursor, self).__init__() + self.setFlag(self.ItemIsFocusable, False) + self.setVisible(False) + self.setZValue(101) + self.mainLine = QtGui.QPolygonF() + + def sizeVerticalCursor(self): + self.mainLine.append(QtCore.QPointF(0.2, -1)) + self.mainLine.append(QtCore.QPointF(1, -1)) + self.mainLine.append(QtCore.QPointF(0, -2)) + self.mainLine.append(QtCore.QPointF(-1, -1)) + self.mainLine.append(QtCore.QPointF(-0.2, -1)) + + self.mainLine.append(QtCore.QPointF(-0.2, 1)) + self.mainLine.append(QtCore.QPointF(-1, 1)) + self.mainLine.append(QtCore.QPointF(0, 2)) + self.mainLine.append(QtCore.QPointF(1, 1)) + self.mainLine.append(QtCore.QPointF(0.2, 1)) + + def paint(self, painter, *_): + pen = QtGui.QPen() + pen.setColor(QtGui.QColor(0, 0, 0, 255)) + pen.setWidthF(0) + pen.setCosmetic(True) + brush = QtGui.QBrush() + brush.setColor(QtGui.QColor(255, 255, 255, 255)) + brush.setStyle(Qt.SolidPattern) + painter.setBrush(brush) + painter.setPen(pen) + painter.drawPolygon(self.mainLine) + + scale = self.scene().parent().transform().m11() + self.setScale(10 / scale) + + def boundingRect(self): + self.bRect = self.mainLine.boundingRect() + return self.bRect.normalized() + + def shape(self): + path = QtGui.QPainterPath() + path.addRect(self.bRect) + return path + + +if __name__ == '__main__': + app = QtWidgets.QApplication([]) + + editor = Editor() + editor.init() + editor.resize(1250, 1250) + editor.show() + + app.exec_()