Using Qt in Plug-ins

Preparing for development

Maya uses a customized version of The Qt Company's Qt framework and ships with its own versions of all the libraries and header files you need to use Qt in your plug-ins. On Linux and OS X the libraries are in Maya's lib directory. On Windows the link libraries are in Maya's lib folder and the DLLs are in Maya's bin folder. On all platforms the header files are shipped as a compressed archive (e.g. qt-5.6.1-include.tar.gz) in the include directory of your Developer Kit installation. Before doing any Qt plug-in work, uncompress the archive into a directory where you have write permission and be sure to include that directory ahead of any others in your include path.

You can create custom UI for Maya using Qt Designer. For Windows and Linux users, Qt Designer is installed with Maya. For Mac OS X users, you can find Qt Designer directly at the Qt Development Tools website; or, you can build Qt from source.

To obtain Qt Designer, you can also install The Qt Company's standard version of Qt on your system or download a copy of the customized Qt source from Autodesk's Open Source web-site (http://www.autodesk.com/lgplsource) and building the tools yourself. If you choose the latter option the download includes text files describing how to configure, build and install Qt for each platform supported by Maya.

Writing The Plug-in

If you were writing your own Qt application from scratch, you would need to create your own QCoreApplication or QApplication instance to handle your application's event loop. When writing a Maya plug-in, you must instead use Maya's own application object which can be retrieved using Qt's qApp macro. The example code below uses the qApp macro to retrieve Maya's application name:

QCoreApplication*  app = qApp;
if (app) {
    cout << "Application name is '" << app->applicationName().toStdString() << "'" << endl;
}

Creating new windows and dialogs using Qt is done the same way as you would if you were writing a standalone Qt application. The helixQtCmd plug-in in the Developer Kit gives a very simple example of this. The plug-in provides the helixQt command which creates a Qt-based button. Whenever the button is pressed it uses API calls to create a helical curve within Maya.

The button is a regular Qt pushButton with a createHelix() slot added to handle the creation of the curve.

class HelixButton : public QPushButton
{
   Q_OBJECT
public:
   HelixButton(const QString& text, QWidget* parent = 0); 
   virtual	~HelixButton();
public slots:
   void	createHelix(bool checked);
};

Whenever the helixQt command is executed, it first checks to see if the button already exists. If not then it creates the button and connects its clicked signal to its createHelix slot, so that whenever the button is clicked the code to create the helix will be executed:

QPointer	HelixQtCmd::button;
MStatus HelixQtCmd::doIt(const MArgList& /* args */)
{
   if (button.isNull()) {
       button = new HelixButton("Create Helix");
       button->connect(button, SIGNAL(clicked(bool)), button, SLOT(createHelix(bool)));
       button->show();
       }

If the button already exists, then the command simply ensures that it is visible and not hidden beneath other windows:

   else {
       button->showNormal();
       button->raise();
       } 
   return MS::kSuccess;
}

Controls created directly using Qt are not in general recognized by Maya's UI commands. The IsUI command does not list them and the commands for specific types of controls, such as button, do not recognize them. The generic control command is an exception, though. So long as the control has been given a unique name, then the control command can be used to test for its existence and perform basic operations on the control such as setting its visibility.

For example, if the createHelix method of the helixQtCmd plug-in had given the button the unique name of "myButton":

   button = new HelixButton("Create Helix");
   button->setObjectName("myButton");

then the following MEL script could be used to hide the button:

if (`control -q -exists myButton`) {
   control -e -visible false myButton;
}

However, the same code using the button command would not work because button only recognizes those buttons which it has created:

if (`button -q -exists myButton`) {      // Will always be false.
   button -e -visible false myButton;   // Will never be executed.
}

It may sometimes be desirable to use Qt to directly access UI elements created using Maya commands. The MQtUtil API class provides methods for retrieving the Qt object underlying a Maya control, layout, window or menu item. For example, if you have a Maya checkBox control named "myCheckBox" which was created with the following MEL script:

window;
columnLayout;
checkBox myCheckBox;
showWindow;

You can use the MQtUtil class to retrieve the checkBox's QWidget pointer, and from that determine the current state of the checkBox:

QWidget* control = MQtUtil::findControl("myCheckBox");
if (control) {
   QCheckBox* cb = qobject_cast(control);
   if (cb) {
      if (cb->isChecked()) {
          MGlobal::displayInfo("myCheckBox is checked");
      } else {
          MGlobal::displayInfo("myCheckBox is not checked");
      }
   }
}

The example above also serves to illustrate why you must always be careful using MQtUtil. The code will only work if Maya's checkbox command creates a QCheckBox widget or something derived from QCheckBox. While that may be the case in the current version of Maya, it could change in some future version. If you write code which uses Qt to directly access UI elements created using Maya commands, then it is up to you to ensure that your code still behaves as expected in each new release of Maya.

Layouts are an even greater minefield of potential problems:

See MQtUtil for a more in-depth discussion of these and other issues surrounding layouts.

Building the plug-in

As noted at the start of this section, Maya ships with customized versions of the Qt libraries and header files. It is important to ensure that the directory containing Maya's version of the headers appears in your include path before those which you may have in a separate Qt installation elsewhere on your system, and that the directory containing Maya's version of the libraries appears in your library path before any others.

It is important to use the version of qmake that is provided with the Maya Developer Kit. After running qmake, a makefile is generated that can be used to build the Qt plug-in.

To build the Qt plug-ins provided with the Maya Developer Kit, follow these steps:

  1. Use the following Qt archives from the Maya installation, and extract them in place.

    You can also obtain the same files from the Maya Developer Kit:

    • include/qt-5.6.1-include.tar.gz
    • mkspecs/qt-5.6.1-mkspecs.tar.gz
    • lib/cmake/qt-5.6.1-cmake.tar.gz
  2. Obtain the plug-in example files from the Maya Developer Kit; for example, the helixQtCmd.cpp, helixQtCmd.h, and helixQtCmd.pro files for the helixQtCmd example.
  3. Create a helixQtCmd directory.

    Copy the aforementioned helixQtCmd.* files to the helixQtCmd directory.

  4. Copy the qtconfig file (from the devkit/plug-ins directory) to the helixQtCmd directory.
  5. Set your MAYA_LOCATION environment variable to point to your Maya installation, and the DEVKIT_LOCATION environment variable to point to the directory where the devkit include, mkspecs, and cmake directories are located.
  6. Navigate to the helixQtCmd directory.
  7. Run the version of qmake that is provided with the Maya Developer Kit.
    $DEVKIT_LOCATION/devkit/bin/qmake helixQtCmd.pro

    A makefile is created as a result. On Mac OS X, the make file is named per plugin with a .mak extension. On Linux and Windows, it is Makefile:

    • Linux/Windows: Makefile
    • Mac OS X: helixQtCmd.mak

    A top level Makefile.qt is provided for Linux and Windows, while a make –fhelixQtCmd.mak is provided for Mac OS X. You use these files to build the default Qt plug-ins.

  8. Run make using the generated Makefile as follows:

    • Linux: make –f Makefile
    • Mac OS X: make –f helixQtCmd.mak
    • Windows: nmake /f Makefile
NOTE:

Makefile requires a qmake project file ending with the extension .pro. Shown below is helixQtCmd.pro, the project file provided in the Developer Kit for the helixQtCmd plug-in. It provides a good example of the straightforward nature of the majority of project files.

include(qtconfig)
TARGET = helixQtCmd
HEADERS += helixQtCmd.h
SOURCES += helixQtCmd.cpp
LIBS += -lOpenMayaUI

The TARGET setting contains the name of the plug-in, excluding its platform-specific extension.

The HEADERS setting contains a space-separated list of all the header files which are part of the plug-in.

The SOURCES setting contains a space-separated list of all the source files which are part of the plug-in.

By default, the plug-in is automatically linked to Maya's Foundation and OpenMaya libraries. If the plug-in needs other libraries, then they should be added to the LIBS setting. Library names should be preceded by '-l' while additional library directories should be specified using '-L'. For example:

LIBS += -L/usr/local/lib -lxml

For more complex needs, please refer to Qt's qmake documentation.

Debugging

All of the example Qt plug-ins are built in release (that is, non-debug) mode. To build them for debug, turn on qmake's debug configuration parameter by doing the following in your .pro file:

CONFIG += debug

That should be sufficient to build your plug-in for debug on Linux and OS X. Unfortunately, on Windows, the debug configuration setting does not just compile your plug-in with debugging information, but it also forces the plug-in to link with the debug versions of the Qt libraries. That makes the plug-in incompatible with Maya, which was linked using the release versions of the Qt libraries. Depending upon which Qt classes your plug-in uses, it may or may not load into Maya, but even if it does successfully load, it is unlikely to perform correctly since it will not have access to Maya's QCoreApplication or other Qt global values.To get around this, set the debug configuration parameter described above and build the intermediate makefiles using the following command, substituting the name of your plug-in for myPlugin:

nmake /f Makefile.qt myPlugin.mak

This generates, among other things, a .mak.Debug file for your plug-in (for example, myPlugin.mak.Debug). Edit that file, find the LIBS line, and replace all of the debug Qt libraries with their non-debug versions by removing the final d from their names. For example, in helixQtCmd.mak.Debug the LIBS line would initially look like this:

LIBS = /LIBPATH:..\..\lib ..\..\lib\OpenMaya.lib ..\..\lib\Foundation.lib ..\..\lib\OpenMayaUI.lib c:\qt-adsk-5.6.1\lib\QtGuid4.lib c:\qt-adsk-5.6.1\lib\QtCored4.lib`

You would replace QtGuid4.lib with QtGui4.lib, and QtCored4.lib with QtCore4.lib, leaving you with this:

LIBS = /LIBPATH:..\..\lib ..\..\lib\OpenMaya.lib ..\..\lib\Foundation.lib ..\..\lib\OpenMayaUI.lib c:\qt-adsk-5.6.1\lib\QtGui4.lib c:\qt-adsk-5.6.1\lib\QtCore4.lib`

Now use the modified .mak.Debug file to explicitly build your plug-in:

nmake /f myPlugin.mak.Debug debug\myPlugin.mll

This still leaves one potential problem area on Windows. Qt's QList template class provides inline methods which can create and destroy Qt objects. Because those methods are inlined, if they are called by plug-in code which has been built for debug, any objects created or deleted will use the memory allocator from the debug version of Microsoft's C Runtime Library. If the methods are called within Maya, they will use the release version of the C Runtime Library. Thus it is possible to get situations where objects are allocated within Maya using the release runtime library and deleted in the plug-in using the debug runtime library, or vice-versa. Since the release and debug versions of Microsoft's C Runtime Library are incompatible with each other, this can lead to crashes.

At the moment, QList is the only Qt class we are aware of which has this potential for failure, but other Qt template classes could exhibit similar problems. To date, the only known workarounds are either to avoid the use of Qt template classes in your code, or to only build your plug-in in release mode on Windows.