6730 lines
282 KiB
Python
6730 lines
282 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 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)()
|