Converting mari Tools from pyside 2 to pyside 6

With Mari 8 going onto the latest VFX Ref Platform, its QT requirements change - namely an update from PySide2 to PySide6. 

Unfortunately this update can have rather major implications to any code written for Mari. 


With Extension Pack being one of the largest Mari Code bases in the world, below I will give some informal hints what to look out for.


Changing Imports

The first relatively easy step is to change the actual module imports to use PySide6.  So for example what was 

 

from PySide2 import QtGui

needs to become

from PySide6 import QtGui

There are some caveats as some modules have moved. Some examples:

PySide 2 PySide 6
 QtWidgets.QAction  QtGui.QAction
QtWidgets.QShortcut QtGui.QShortcut
QtWidgets.QOpenGlWidget QtOpenGLWidgets.QOpenGLWidget

You can use a tool called pysideup to handle these changes

pip install pysideup
pysideup your_project_folder

As any import error usually shows up right on Mari Launch, I ended up doing it manually instead, first doing a PySide2->PySide6 Search and Replace and then fixing any remaining Import Errors that got introduced by moved modules


QT Enum Changes - no more inheritance to main class

Let's say you used a Qt Layout Alignment before

Qt.AlignCenter

that would be an enum (not a  function() ) invoking a specific behavior. There are a lot of these in QT. 

AlignCenter actually even in PySide2 never lived under Qt. It always lived under

Qt.AlignmentFlag.AlignCenter

 but the way PySide2 inherited themto the top classes it was accessible from the base level ..

so you could write Qt.AlignCenter and it would correctly inherit from Qt.AlignmentFlag.AlignCenter.

This is now no longer the case. Enums will not be inherited by classes.
That means you will have to check every single Qt Enum that was accessed from the top class and set it correctly

Qt.AlignCenter -> Qt.AlignmentFlag.AlignCenter

There is a tool 

pip install pyside-migrate
pyside-migrate [your_project_folder]


it does an ok job at converting many occurrences, but equally misses as many, so code will still need to be thoroughly checked.
Often times in your IDE you can see the typing throwing warnings about non-existent enums anymore.
So make sure to have a Python Interpreter with PySide6 active in your Code Editor so you can more easily spot the warnings.

While pyside-migrate catches some obvious occurrences, there are sometimes "hidden" uses of enums that aren't quite as obvious.

Let's say you subclassed a widget

 

class MyGraphicsItem(QtWidgets.QGraphicsItem)


Inside you then call the enum of QGraphicsItem, but since you instanced it you call it as "self".

self.setFlags(self.flags() & ~self.ItemIsFocusable)

 

This is now no longer possible in PySide 6. Instead you need to call the original Class Enum

self.setFlags(self.flags() & ~QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)


As cases of these are not always immediately obvious, this is unfortunately a very tedious job (yes, I tried multiple AI analysis - it tends to miss a bunch still)


QT Enum Moves

Some enums also have flat-out moved into subclasses. For example I was reasonably sure that for message boxes there previously wasn't a icon class. But now there is ..

PySide2

QtWidgets.QMessageBox.Critical

PySdie6

QtWidgets.QMessageBox.Icon.Critical


Mari ENum Changes

Mari Enums are equally affected by the class inheritance changes - and there's no tool to auto fix it. 
So for example 

mari.Image.DEFAULT_OPTIONS

needs to become

mari.Image.SaveOptions.DEFAULT_OPTIONS

To help with identifying the Mari enums, I generated a full list of Python Stubs for Mari 8. This way you can at least see the warnings about non-existent enums. You can find them here on GitHub

As with QT Enums, the trouble starts in cases like this where you reassigned the original class to a variable and access the enums from there

PySide2

channel = mari.current.channel()
ocio_config = channel.colorSpaceConfig()
ocio_config.setColorspace(ocio_config.COLORSPACE_STAGE_NATIVE,'raw')

in the above case the code needs to change to 

PySide6

channel = mari.current.channel()
ocio_config = channel.colorSpaceConfig()
ocio_config.setColorspace(mari.ColorspaceConfig.ColorspaceStage.COLORSPACE_STAGE_NATIVE,'raw')


Due to the sometimes very hidden nature of these assignments, I found it best to identify ALL fully uppercase words inside the codebase and go through them one by one to fix them.

A small complication of this, is when the same Enum Name exists in multiple places e.g. the enum 


DEPTH_FLOAT

can exist as 

mari.Image.Depth.DEPTH_FLOAT
mari.PaintBuffer.BufferDepth.DEPTH_FLOAT
mari.Projector.BitDepth.DEPTH_FLOAT


in these cases you will have to be mindful of the context the enum is used (Image, Buffer, Projector) and set the correct one


Mari Literal Enums have vanished

There are a few edge cases where literal Mari enums have completely disappeared. Nearrly 100% of all cases I've seen so far involve the use of mari.resources. E.g. before in order to access the Mari Icon Path you would do

mari.resources.path(mari.resources.ICONS)

the literal "ICONS" no longer exists. Instead you need to call the function returning the string:

mari.ResourceInfo.iconPathKey()


Use Linters !

Especially for the Enum Changes for Mari and PySide 6 using Linters in your Code Editor will help you spot issues.
For Mari API you can use the dedicated Python Stubs (ensure you are using Mari 8+ stubs !).
You still have to check every file but at least this way you can actually find them relatively easy

 

Linter Squiggles immediately alert you to a potential issue
Linter Squiggles immediately alert you to a potential issue

QT Mouse event changes

Mouse events like "event.pos()" previously returned a integer but now return a float. This is an easy fix usually as you can go back to the original via

event.pos().toPoint()


Some functions like "globalPos()" also are affected by this change and return a float by default now (and can be converted back to point using toPoint()).

Speaking of Mouse things, wheel evaluation has also changed. Mouse Wheel

event.delta()


no longer exists and needs to be replaced with

event.angleDelta().y()


QRegExp needs to be changed to QREGULAREXPRESSION

The module QRegExp has been replaced by QRegularExpression.

The new module has a different set of functions so in general you won't be able to simply convert QRegExp to the new ones, requiring quite some rewrites to get the same result


QtWidgets.qapp is gone

QtWidgets.qApp()

 

has been removed. You can usually use QtWidgets. QApplication instead


Changes to font weight

In PySide2 you could set font weights via integers

 

font.setWeight(75)

this is no longer supported. Instead PySide6 exposes different enums for different weights. The above weight 75 corresponds to

 

font.setWeight(QFont.Weight.Bold)


QImage Alpha changes

There are multiple behavior changes to QImages and their Alphas.

There is no direct function anymore to retrieve an alpha channel so
 

image.alphaChannel()

..simply doesn't exist anymore

 

In addition QImage Alpha Channels are handling image processing operations very differently now when working with image formats like PNG where you can save transparency without an explicit Alpha channel
While QPixmaps seem to stick to their old PySide 2 behavior, doing pixel processing on a PNG with transparency breaks the transparency and instead shows the native color (black/white) in those areas.

An example of this is the following in my code base:

  • I have several image utils functions that can desaturate or darken Images (for use in icons)
  • In the initial unchanged PySide6 conversion all those images lost their alpha channel and displayed with a solid background

The fix for me was to go through a pixmap first which seemed to maintain it

original_pixmap = QPixmap(path)
original_image = original_pixmap.toImage().convertToFormat(QImage.Format.Format_ARGB32)


as there's no more easy way to "store" the original alpha channel (no more image.alphaChannel() ) this approach seemed the least work