Hi guys, today I would like to share with you a script that I found useful in many situations, for instance when you model a mesh and start placing all over the scene, remembering just in the end that you didn’t have UVs on that.
We can solve this problem using the Transfer Attribute tool, but since it works with just 2 meshes selected, we have to apply it for each object in the scene, keeping a source one selected and caring for the history to avoid errors.
This is tedious sometimes and really a time waste, so I wrote a script which iterates a list of selected objects, using the first selected as source and the others as targets in the transfer attribute function.
The script just implements UV sets, sample and delete history choice, feel free to use my source as a base for your work if you want to extend it.
This is the UI of our script:
Let’s have a look at the .py files.
The first one is BaseWindow.py, this is the definition of the BaseWindow class.
(If you see double imports and reload calls at the top of the codes, that’s for reloading modules and allow editing without re-opening Maya).
import maya.cmds as cmds #define a base window UI class IA_BaseWindow(object): @classmethod def showUI(cls): win = cls() win.create() return win #inizialization def __init__(self, **kwargs): self.window = kwargs.setdefault('handle', 'IA_baseWindow') self.title = kwargs.setdefault('title', 'IA_baseWindowTitle') self.size = ( 546, 350) self.actionName = 'Apply and Close' def create(self): if cmds.window(self.window, exists = True): cmds.deleteUI(self.window, window = True) self.window = cmds.window( self.window, title = self.title + ' | %s' %self.window, widthHeight = self.size, menuBar = True, ) #set layout and attachment rules for responsive resizing self.mainForm = cmds.formLayout(nd = 100) self.commonMenu() self.commonButtons() self.optionsBorder = cmds.tabLayout( scrollable = True, tabsVisible = False, height = 1 ) cmds.formLayout( self.mainForm, e = True, attachForm = ( [self.optionsBorder, 'top', 0], [self.optionsBorder, 'left', 2], [self.optionsBorder, 'right', 2] ), attachControl = ( [self.optionsBorder, 'bottom', 5, self.applyBtn] ) ) self.optionsForm = cmds.formLayout(nd=100) self.displayOptions() cmds.showWindow() #menu definition def commonMenu(self): self.helpMenu = cmds.menu (label = 'Help') self.helpMenuItem = cmds.menuItem( label ='Help on %s' %self.title, command = self.helpMenuCmd ) #commands #buttons def commonButtons(self): self.commonBtnSize = ((self.size[0]-18)/3, 26) self.actionBtn = cmds.button( label = self.actionName, height = self.commonBtnSize[1], command = self.actionBtnCmd ) self.applyBtn = cmds.button( label='Apply', height = self.commonBtnSize[1], command = self.applyBtnCmd ) self.closeBtn = cmds.button( label = 'Close', height = self.commonBtnSize[1], command = self.closeBtnCmd ) #attachment rules for the buttons cmds.formLayout( self.mainForm, e = True, attachForm = ( [self.actionBtn, 'left' , 5], [self.actionBtn, 'bottom' , 5], [self.applyBtn, 'bottom' , 5], [self.closeBtn, 'right' , 5], [self.closeBtn, 'bottom' , 5] ), attachPosition = ( [self.actionBtn, 'right', 1,33], [self.closeBtn, 'left', 0,67] ), attachControl = ( [self.applyBtn, 'left', 4, self.actionBtn], [self.applyBtn, 'right', 4, self.closeBtn] ), attachNone = ( [self.actionBtn, 'top'], [self.applyBtn, 'top'], [self.closeBtn, 'top'] ) ) #help menu def helpMenuCmd(self, *args): cmds.launch(web='http://ue4techarts.com') #buttons commands def actionBtnCmd(self, *args): self.applyBtnCmd() self.closeBtnCmd() def applyBtnCmd(self, *args): pass def closeBtnCmd(self, *args): cmds.deleteUI(self.window, window = True) #dispay : for overriding, displays the content of the window def displayOptions(self): pass
This class define a BaseWindow object which will be used to create our child Window later, it defines some attributes like window handler, title and size.
cmds.formLayout was used to define attachment rules of the buttons, to keep them always at the bottom and stretch based on window size.
The only button logic which has been programmed here is the close, which simply delete the UI and returns to Maya.
Display options method needs to be overridden in the child class.
Let’s look at our child now: TransferAttributesSeqWindow.py
import maya.cmds as cmds #double import for reloading import BaseWindow reload (BaseWindow) from BaseWindow import IA_BaseWindow import TransferAttrsToAll reload(TransferAttrsToAll) class TransferAttributeSeqsWindow(IA_BaseWindow): def __init__ (self): IA_BaseWindow.__init__(self) self.title = 'Transfer Attributes Sequentially' self.actionName = 'Transfer' self.size = (800, 400) #override, creates radio buttons def displayOptions(self): self.attrsToSendGrp = cmds.frameLayout( label = 'Attributes to Transfer', collapsable = False ) self.uvTypeLabels = cmds.radioButtonGrp( label = 'Uv Sets: ', labelArray3 = [ 'Off', 'Current', 'All' ], numberOfRadioButtons = 3, select = 3 ) self.attrsSettGrp= cmds.frameLayout( label = 'Attributes Settings', collapsable = False ) self.sampleSpaceLabels = cmds.radioButtonGrp( label = 'Sample: ', labelArray4 = [ 'World', 'Local', 'UV', 'Topology' ], numberOfRadioButtons = 4, select = 1, columnWidth = [50, 50] ) self.meshSettings= cmds.frameLayout( label = 'Mesh Settings', collapsable = False ) self.deleteHistoryCheck = cmds.checkBox( label = 'Delete History before applying', al = 'center' ) #override def applyBtnCmd(self, *args): uvSelection = cmds.radioButtonGrp( self.uvTypeLabels, q= True, select = True ) #remapped based on transferAttrs command values self.ssIndxRemap = { 1: 0, 2: 1, 3: 3, 4: 5 } sampleSpaceSelection = cmds.radioButtonGrp( self.sampleSpaceLabels, q= True, select = True ) deleteHistorySelection = cmds.checkBox( self.deleteHistoryCheck, q= True, value = True ) transferAttrsToAll( uvs = uvSelection-1, sampleSpace = self.ssIndxRemap[sampleSpaceSelection], deleteHistory = deleteHistorySelection )
This is the class I created inheriting from BaseWindow, I changed the title and override methods for what I wanted to do today.
I started overriding displayOptions, to implement my script UI: two radio button groups to input uv type and sample space type, and a checkbox to delete history before proceeding (I suggest to use this as transfer attribute sometimes can break if you have dirty history).
Sample space needed to be remapped using a dictionary, since the radioButtonsGroups starts counting their elements from 1 and not from 0, same for uv but in its case I simply subtract 1 from its value.
In the end I call the function transferAttrsToAll from my TransferToAll module, passing all the info I got from the UI.
This is the code of the sequential transfer attribute:
import maya.cmds as cmds def transferAttrsToAll(**kwargs): #if selection is full go ahead passing the radios button results to the function trInfo = kwargs selected = cmds.ls(sl=True) if trInfo['deleteHistory']: for i in range(len(selected)): cmds.delete(selected[i], ch=True) if len(selected) > 0: targetObjects, sourceObject = selected[1:], selected[0] for targetObject in targetObjects : cmds.transferAttributes(sourceObject, targetObject, uvs = trInfo['uvs'], sampleSpace = trInfo['sampleSpace']) else: print 'Please, select at least two objects.'
To use this script, download it from my mega here:
Mega – Transfer Attributes Sequentially
and follow the instructions inside.
If you have any doubts or problems please contact me or write a comment.
Bye!