1427 lines
51 KiB
Python
1427 lines
51 KiB
Python
"""
|
|
|
|
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)
|