""" 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)()