[Maya – Python] Transfer Attributes sequentially with UI

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.

01

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:

02

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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s