I have designed a PySide interface for the 3D rendering software package Maya whose purpose is to take a file with animation on a single stream and break it up and export each range of animation. I'm pretty new to PySide and would like just a general overview (or harsh critique) of what I could do to improve the quality of this code. It's not entirely finished so some features of the UI don't actually hook up to anything yet such as the tpose box.from PySide import QtGui, QtCorefrom maya import OpenMayaUI as omuifrom shiboken import wrapInstanceimport pymel.core as pmimport osdef mayaMainWindow():    mainWinPtr = omui.MQtUtil.mainWindow()    return wrapInstance(long(mainWinPtr), QtGui.QWidget)class ExportBar(QtGui.QFrame):    def __init__(self, parent=None):        super(ExportBar, self).__init__(parent)        self.parent = parent        print parent.width(), parent.height()        self.resize(parent.width() - 30, 75)        self.widgets = [        QtGui.QLineEdit(),        QtGui.QSpinBox(),        QtGui.QLabel(-),        QtGui.QSpinBox(),        QtGui.QPushButton(        Export        ),        QtGui.QPushButton(Del)        ]        self.widgets[1].setMaximum(pm.playbackOptions(max=1, q=1))        self.widgets[3].setMaximum(pm.playbackOptions(max=1, q=1))        self.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised)        self.setLineWidth(2)        self.initUI()    def initUI(self):        mainLO = QtGui.QHBoxLayout()        self.setLayout(mainLO)        self.widgets[0].setPlaceholderText(File Name)        self.widgets[-2].clicked.connect(lambda: exportAnimation(self))        self.widgets[-1].clicked.connect(self.deleteWidget)        for w in self.widgets:            mainLO.addWidget(w)    def deleteWidget(self):        self.parent.exportBars.remove(self)        self.close()        self.parent.holder.resize(self.parent.holder.width(), self.parent.holder.height() - self.height())        if self.parent.exportBars == []:            self.parent.cycleRow.newExportBar()class FileBrowser(QtGui.QWidget):    def __init__(self, parent=None):        super(FileBrowser, self).__init__(parent)        self.parent = parent        self.resize(parent.width(), 20)        mainLO = QtGui.QHBoxLayout()        self.setLayout(mainLO)        mainLO.setSpacing(10)        self.pathBar = QtGui.QLineEdit(parent.workScenePath)        self.pathBar.textChanged.connect(self.changeWorkPath)        fileButton = QtGui.QPushButton(Browse Files)        fileButton.clicked.connect(self.changePath)        mainLO.addWidget(self.pathBar)        mainLO.addWidget(fileButton)    def changePath(self):        newPath = pm.fileDialog2(fileMode=3, okc=Select)        try:            self.pathBar.setText(newPath[0])        except Exception:            pass    def changeWorkPath(self):        self.parent.workScenePath = self.pathBar.text()class CycleRow(QtGui.QWidget):    def __init__(self, parent=None):        super(CycleRow, self).__init__(parent)        self.resize(parent.width(), 20)        self.parent = parent        mainLO = QtGui.QHBoxLayout()        mainLO.setContentsMargins(0, 0, 0, 0)        mainLO.setSpacing(25)        mainLO.addSpacing(2)        self.setLayout(mainLO)        self.newExpBarButton = QtGui.QPushButton(              New Cycle List              )        self.newExpBarButton.clicked.connect(self.newExportBar)        self.tPoseFrameBox = QtGui.QSpinBox()        self.tPoseFrameBox.setContentsMargins(0, 0, 70, 0)        self.tPoseFrameBox.valueChanged.connect(self.changeTPoseFrame)        expAllButton = QtGui.QPushButton(                            Export All                            )        expAllButton.setContentsMargins(0, 0, 0, 0)        expAllButton.clicked.connect(lambda: exportAllAnimations(parent))        label = QtGui.QLabel(T Pose Frame)        label.setContentsMargins(0, 0, 0, 0)        mainLO.addWidget(label)        mainLO.addWidget(self.tPoseFrameBox)        mainLO.addWidget(self.newExpBarButton)        mainLO.addWidget(expAllButton)    def newExportBar(self):        exportBar = ExportBar(self.parent)        self.parent.holderLO.addWidget(exportBar)        self.parent.exportBars.append(exportBar)        self.parent.holder.resize(self.parent.holder.width(), self.parent.holder.height() + exportBar.height())    def changeTPoseFrame(self):        self.parent.tPoseFrame = self.tPoseFrameBox.text()class AnimationCycleSplitter(QtGui.QWidget):    def __init__(self, parent=mayaMainWindow()):        super(AnimationCycleSplitter, self).__init__(parent)        self.workScenePath = os.path.join(pm.workspace.path, scenes\\)        self.setWindowFlags(QtCore.Qt.Window)        self.setGeometry(300, 300, 600, 350)        self.setWindowTitle(Animation Cycle Splitter)        self.exportBars = []        self.initUI()    def initUI(self):        mainLO = QtGui.QVBoxLayout()        mainLO.setSpacing(10)        self.setLayout(mainLO)        browser = FileBrowser(self)        self.holder = QtGui.QWidget()        self.holder.resize(self.width() - 30, 0)        self.holderLO = QtGui.QVBoxLayout()        self.holderLO.setContentsMargins(0, 0, 0, 0)        self.holder.setLayout(self.holderLO)        scrollArea = QtGui.QScrollArea()        scrollArea.setWidget(self.holder)        self.cycleRow = CycleRow(self)        self.tPoseFrame = self.cycleRow.tPoseFrameBox.value()        self.cycleRow.newExportBar()        mainLO.addWidget(browser)        mainLO.addWidget(scrollArea)        mainLO.addWidget(self.cycleRow)        self.show()class Keyframe(object):    def __init__(self, objs):        self._frame = pm.currentTime()        self._values = {attr:attr.get() for obj in objs for attr in obj.listAnimatable()}        self._objs = objs    def paste(self, frame):        pm.currentTime(frame)        for attr in self._values.keys():            try:                attr.set(self._values[attr])            except RuntimeError:                pass        pm.setKeyframe(self._objs)def exportAllAnimations(gui, fileType=.ma):    for ebar in gui.exportBars:        exportAnimation(ebar, fileType=fileType)        def getKeysForFramerange(beg, end, joints):    keys = []    for num, frame in enumerate(range(beg, end)):        pm.currentTime(frame)        keys.append(Keyframe(joints))    return keysdef pasteKeysForFramerange(keys):    for frame, key in enumerate(keys):        key.paste(frame)def exportAnimation(gui, fileType=.ma):    pm.currentTime(gui.tPoseFrame)    joints = pm.ls(type=joint)    tPose = [Keyframe(joint) for joint in joints]    pm.select(joints)    fullPathToFile = os.path.join(gui.parent.workScenePath, gui.widgets[0].text() + fileType)    beg, end =  gui.widgets[1].value(), gui.widgets[3].value()    keys = getKeysForFramerange(beg, end, joints)    pm.copyKey()    pm.cutKey()    pasteKeysForFramerange(keys)    pm.currentTime(0)    pm.exportAll(fullPathToFile, force=1)    pm.pasteKey()
PySide interface for Autodesk Maya
python;maya;pyside
I don't know much about Maya (I've only programmed 3ds Max) so this is a generic review.There are no docstrings. What do all these classes and methods do? How are they supposed to be used? Imagine someone having to maintain this code when you're no longer working on it. What questions would they want to ask you?Be careful with import X as Y: unless Y is a very well known abbreviation for X (such as np for numpy) then this makes your code harder for people to read and understand. In this case:from maya import OpenMayaUI as omuiyou only use omui once, so I think that you don't really gain anything by this abbreviation.Where do the numbers (30, 75, 20, etc.) come from? If these are related to things like the heights of the default fonts, it would be better to calculate these by calling QFontInfo.pixelSize() or whatever. This would make it clear what the numbers mean.It seems to me that there ought to a better way to get a wider button than by padding the label with spaces.There seems to be some confusion about the choice of directory separator. The screenshot shows that pm.workspace.path uses forward slashes but you've used os.path.join which uses native (backwards) slashes.You maintain an exportBars list containing the ExportBar instances in holderLO. So every time you change the latter you have to keep the former in sync. Why not use holderLO.children() and avoid the need to maintain two copies of this data?You copy a key and then cut it:pm.copyKey()pm.cutKey()but if I understand the documentation rightly, cutKey also copies to the clipboard, so is the copyKey call really necessary?Why do you put some initialization code in __init__ and some in initUI? There doesn't seem to be a principled separation here.The exportAnimation function needs to know all about the internals of the ExportBar class, so surely it ought to be a method on that class. You could then write:self.widgets[-2].clicked.connect(self.exportAnimation)Writing self.widgets[X] is quite unclear (which button is number 3, again?). It would be clearer to write the initialization code like this:export_button = QtGui.QPushButton(        Export        )export_button.clicked.connect(self.exportAnimation)delete_button = QtGui.QPushButton(Del)delete_button.clicked.connect(self.deleteWidget)self.widgets = [..., export_button, delete_button]The exportAllAnimations function needs to know about the implementation of the AnimationCycleSplitter class, so surely it ought to be a method on that class.The FileBrowser class manages a path that is stored in parent.workScenePath. This seems wrong to me: what if you wanted to attach multiple FileBrowser objects to the same parent? It would be better for the FileBrowser class to store the path in one of its own attributes, and then the AnimationCycleSplitter class can provide a method that fetches the path from the FileBrowser instance.It seems pointless to have keyword arguments like fileType=.ma on functions like exportAnimation which are attached to buttons in the GUI. There is no way for the user to pass values for these keyword arguments, so what is their purpose? A global variable would be clearer. (Also, fileType ought to be named fileExtension or something similar.)There are a few places where I would appreciate a comment. (i)what is the purpose of the cutKey and pasteKey operations? Is this to preserve the current selection? But won't it clobber the clipboard? (ii)Why might you get a RuntimeError in Keyframe.paste?