# -= ml_controlLibrary.py =- # __ by Morgan Loomis # ____ ___ / / http://morganloomis.com # / __ `__ \/ / Revision 4 # / / / / / / / 2018-02-17 # /_/ /_/ /_/_/ _________ # /_________/ # # ______________ # - -/__ License __/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Copyright 2018 Morgan Loomis # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to use, # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the # Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # ___________________ # - -/__ Installation __/- - - - - - - - - - - - - - - - - - - - - - - - - - # # Copy this file into your maya scripts directory, for example: # C:/Documents and Settings/user/My Documents/maya/scripts/ml_controlLibrary.py # # Run the tool in a python shell or shelf button by importing the module, # and then calling the primary function: # # import ml_controlLibrary # ml_controlLibrary.ui() # # # __________________ # - -/__ Description __/- - - - - - - - - - - - - - - - - - - - - - - - - - - # # Export and import nurbs curves to be used as animation controls. # # ____________ # - -/__ Usage __/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Run the UI. The first time it is run, it will prompt to create a control # repository directory if it doesn't find one. This is where control curves will # be saved, and by default it will be in the same directory that the script is in. # If you want them saved somewhere else, just set the REPOSITORY_PATH variable in # this file. # # _________ # - -/__ Ui __/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # : Right-click for more options # # ___________________ # - -/__ Requirements __/- - - - - - - - - - - - - - - - - - - - - - - - - - # # This script requires the ml_utilities module, which can be downloaded here: # https://raw.githubusercontent.com/morganloomis/ml_tools/master/ml_utilities.py # # __________ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /_ Enjoy! _/- - - __author__ = 'Morgan Loomis' __license__ = 'MIT' __category__ = 'None' __revision__ = 4 import os, shutil from functools import partial import maya.cmds as mc import maya.mel as mm try: import ml_utilities as utl utl.upToDateCheck(32) except ImportError: result = mc.confirmDialog( title='Module Not Found', message='This tool requires the ml_utilities module. Once downloaded you will need to restart Maya.', button=['Download Module','Cancel'], defaultButton='Cancel', cancelButton='Cancel', dismissString='Cancel' ) if result == 'Download Module': mc.showHelp('http://morganloomis.com/tool/ml_utilities/',absolute=True) try: import ml_parentShape except ImportError: raise ImportError('This module requires ml_parentShape in order to work. Please download from http://morganloomis.com') REPOSITORY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ml_controlCurveRepository').replace('\\','/') def ui(): '''Launch the UI ''' if not os.path.exists(REPOSITORY_PATH): result = mc.confirmDialog( title='Control Repository Not Found', message='Create a repository directory?', button=['Create','Cancel'], defaultButton='Cancel', cancelButton='Cancel', dismissString='Cancel' ) if result != 'Create': return None os.mkdir(REPOSITORY_PATH) win = ControlLibraryUI() win.buildMainLayout() win.finish() def controlFilePath(name): '''Simply return the expected path for a given control name. ''' if not os.path.exists(REPOSITORY_PATH): raise IOError("Repository doesn't exist: {}".format(REPOSITORY_PATH)) return os.path.join(REPOSITORY_PATH, name+'.ctrl').replace('\\','/') class ControlLibraryUI(utl.MlUi): '''Inherited from MlUi ''' def __init__(self): super(ControlLibraryUI, self).__init__('ml_controlLibrary', 'Control Library', width=400, height=400, info='''Import and Export control curves. If there's no controls available in the import tab, you'll want to export one first!''') #Repo: {}'''.format(REPOSITORY_PATH)) self.buildWindow() def buildMainLayout(self): '''Build the main part of the ui ''' tabs = mc.tabLayout() tab1 = mc.columnLayout(adj=True) mc.scrollLayout(cr=True) self.shelfLayout = mc.shelfLayout() self.refreshShelfLayout() mc.setParent(tabs) tab2 = mc.columnLayout(adj=True) mc.separator(height=8, style='none') mc.text('Select curve(s) to export. Multiple selected curves will be combined.') mc.text('Center and fit the curve in the viewport,') mc.text('and make sure nothing else is visible for best icon creation.') mc.separator(height=16, style='in') mc.button('Export Selected Curve', command=self.exportControl, annotation='Select a nurbsCurve to export.') mc.tabLayout( tabs, edit=True, tabLabel=((tab1, 'Import'), (tab2, 'Export') )) if not mc.shelfLayout(self.shelfLayout, query=True, numberOfChildren=True): mc.tabLayout( tabs, edit=True, selectTab=tab2) def exportControl(self, *args): '''Wrapper to export a control and refresh the ui. ''' promptExportControl() self.refreshShelfLayout() def refreshShelfLayout(self, *args): '''Delete and the shelf buttons and remake them ''' shelfButtons = mc.shelfLayout(self.shelfLayout, query=True, childArray=True) if shelfButtons: for child in shelfButtons: mc.deleteUI(child) mc.setParent(self.shelfLayout) for each in os.listdir(REPOSITORY_PATH): if each.endswith('.ctrl'): name = os.path.splitext(each)[0] icon = None imageFile = os.path.join(REPOSITORY_PATH,name+'.png') if os.path.isfile(imageFile): icon = imageFile filename = os.path.join(REPOSITORY_PATH,each) button = mc.shelfButton(command=partial(importControl, name), image=icon, width=70, height=70, imageOverlayLabel=name.replace('_',' ').replace(' ',' '), annotation=name) menus = mc.shelfButton(button, query=True, popupMenuArray=True) if menus: for menu in menus: mc.deleteUI(menu) #mc.popupMenu() #mc.menuItem('delete', command=partial(self.deleteShelfButton, name)) def deleteShelfButton(self, name, *args): '''Delete the shelf button ''' #at the moment this crashes my maya. Need to investigate before including. path = controlFilePath(name) os.remove(path) os.remove(path.replace('.ctrl','.png')) self.refreshShelfLayout() def exportControl(curves, name): '''Export a control curve ''' if not isinstance(curves, (list, tuple)): curves = [curves] grp = mc.group(em=True, name=name) for each in curves: ml_parentShape.parentShape(each, grp) mc.delete(grp, constructionHistory=True) tempFile = mc.internalVar(userTmpDir=True) tempFile+='tempControlExport.ma' mc.select(grp) mc.file(tempFile, force=True, typ='mayaAscii', exportSelected=True) with open(tempFile, 'r') as f: contents = f.read() ctrlLines = ['//ML Control Curve: '+name] record = False for line in contents.splitlines(): if line.startswith('select'): break if line.strip().startswith('rename'): #skip the uuid commands continue if line.startswith('createNode transform'): record = True ctrlLines.append('string $ml_tempCtrlName = `createNode transform -n "'+name+'_#"`;') elif line.startswith('createNode nurbsCurve'): ctrlLines.append('createNode nurbsCurve -p $ml_tempCtrlName;') elif record: ctrlLines.append(line) with open(controlFilePath(name), 'w') as f: f.write('\n'.join(ctrlLines)) return grp def promptExportControl(*args): '''Export selection, prompt for name, and create icon as well. ''' sel = mc.ls(sl=True) assert sel, 'Select a control curve(s) to export.' for each in sel: if mc.nodeType(each) == 'nurbsCurve': continue shapes = mc.listRelatives(each, shapes=True, type='nurbsCurve') assert shapes, '{} is not a nurbsCurve'.format(each) result = mc.promptDialog( title='Export Control Curve', message='Enter Name:', button=['OK', 'Cancel'], defaultButton='OK', cancelButton='Cancel', dismissString='Cancel') if result != 'OK': return ctrlName = mc.promptDialog(query=True, text=True) ctrlName = ''.join(x if x.isalnum() else '_' for x in ctrlName) if os.path.exists(controlFilePath(ctrlName)): result = mc.confirmDialog(title='Control Exists', message='A control of this name already exists.', button=['Overwrite','Cancel'], defaultButton='Cancel', cancelButton='Cancel', dismissString='Cancel' ) if result != 'Overwrite': return ctrl = exportControl(sel, ctrlName) strokes = mc.ls(type='stroke') #create the icon mc.ResetTemplateBrush() brush = mc.getDefaultBrush() mc.setAttr(brush+'.screenspaceWidth', 1) mc.setAttr(brush+'.distanceScaling', 0.01) mc.setAttr(brush+'.color1', 0.1, 0.65, 1, type='double3') mc.select(ctrl) mc.AttachBrushToCurves(ctrl) image = utl.renderShelfIcon(name=ctrlName, width=64, height=64) imagePath = os.path.join(REPOSITORY_PATH, os.path.basename(image)) shutil.move(image, imagePath) #delete new strokes. newStrokes = [x for x in mc.ls(type='stroke') if x not in strokes] for each in newStrokes: mc.delete(mc.listRelatives(each, parent=True, pa=True)) def importControl(name): '''Import a control file based on name ''' path = controlFilePath(name) if not os.path.isfile(path): raise IOError('File not found: {}'.format(path)) assPre = mc.ls(assemblies=True) #sourcing this file creates the control curve. mm.eval('source "{}"'.format(path)) assPost = mc.ls(assemblies=True) for each in assPre: assPost.remove(each) mc.select(assPost[0]) return assPost[0] # ______________________ # - -/__ Revision History __/- - - - - - - - - - - - - - - - - - - - - - - - # # Revision 1: 2016-07-31 : First publish. # # Revision 2: 2016-08-02 : fixing function order for correct header generation. # # Revision 3: 2016-08-11 : documentation and dependency update # # Revision 4: 2018-02-17 : Updating license to MIT.