Using PySide2

Autodesk 3ds Max ships with a pre-built version of PySide 2.0 compatible with Python 2.7.15. This version includes all standard PySide modules.

Example PySide Script

The following simple example shows how to obtain a handle for the PySide application object and create a dockable widget:

'''
Demonstrates how to ceate a QDockWidget with PySide2 for use in 3ds Max
'''

from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets
import pymxs

def make_cylinder():
    cyl = pymxs.runtime.Cylinder(radius=10, height=30)
    pymxs.runtime.redrawViews()
    return    
    
class PyMaxDockWidget(QtWidgets.QDockWidget):
    def __init__(self, parent=None):
        super(PyMaxDockWidget, self).__init__(parent)
        self.setWindowFlags(QtCore.Qt.Tool)
        self.setWindowTitle('Pyside Qt  Dock Window')
        self.initUI()
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        
    def initUI(self):
        main_layout = QtWidgets.QVBoxLayout()
        label = QtWidgets.QLabel("Click button to create a cylinder in the scene")
        main_layout.addWidget(label)

        cylinder_btn = QtWidgets.QPushButton("Cylinder")
        cylinder_btn.clicked.connect(make_cylinder)
        main_layout.addWidget(cylinder_btn)
        widget = QtWidgets.QWidget()
        widget.setLayout(main_layout)
        self.setWidget(widget)
        self.resize(250, 100)    

def main():
    pymxs.runtime.resetMaxFile(pymxs.runtime.name('noPrompt'))
    mainWindow=QtWidgets.QWidget.find(pymxs.runtime.windows.getMAXHWND())
    w = PyMaxDockWidget(parent=mainWindow)
    w.setFloating(True)
    w.show()

if __name__ == '__main__':
    main()

Notes:

Loading Qt UI Files

MaxPlus provides a facility for loading UI files created by Qt Designer: MaxPlus.LoadUiType(). This example looks for a ui file named test.ui located under scripts/Python/ui/ and loads it.

Note: If you use the Python global __file__ to find the target ui file, the global will only evaluate when using the Scripting > Run Script command. In this example, we use the MAXScript getDir() function, which will work in any context.

import MaxPlus, os
from PySide2 import QtWidgets
from PySide2 import QtCore
from pymxs import runtime as rt

def makeTeapot():
    rt.teapot()
    
class TestDialog(QtWidgets.QDialog):
    def __init__(self, ui_class, parent=MaxPlus.GetQMaxMainWindow()):
        QtWidgets.QDialog.__init__(self, parent)
        self.ui=ui_class()
        self.ui.setupUi(self)
        self.ui.pushButton.clicked.connect(makeTeapot)

def main():
    script_dir = rt.getDir(rt.name('scripts'))  
    ui_path = os.path.join(script_dir, 'Python', 'ui','test.ui')
    ui_class, base_class = MaxPlus.LoadUiType(ui_path)
    instance = TestDialog(ui_class)
    instance.show()


if __name__ == '__main__':
    main()