[PYTHON] One for Maya UI

Maya Advent Calendar 2019

This year is also an ad-care one stroke.

This year is on time.

This time, it's a UI story that changes my taste so far. It looks good.

I value playfulness when making tools,

Whenever I make a new tool, I add new elements and Easter eggs.

Especially when I'm making a UI, I want to make something that looks fun to use.



So, this sample. coordUI_00.gif Using this material, you can create a UI that can do something like character makeup.

In the first place

The UI commands provided by Maya (hereinafter referred to as Maya UI in this article) are out of reach of the itch.

For example, in this sample, the picture </ font> command used to display images.

This will display the size of the source image as it is.

There is no problem if the size of the source image is the same,

If possible, I would like to flexibly scale and use it when the size is different.

You may also want to display a small thumbnail display.

In other words, I want the code to control the display independently of the source.

I can't control a little with other UI commands,

There is a feeling of itching that "the itch is out of reach".

That said, there are some good things about the Maya UI.

"If you hit a command, it will come out with a good feeling without specifying details."

For those who are not used to making it, Maya UI is annoying, but

I think it's easier to interact with PyQt and PySide when creating a UI with just a few buttons. (* Of course, some people find PyQt and PySide easier.)

So, the purpose of this article.

** "Make a slightly elaborate UI while keeping the Maya UI comfortable" **

Easy to do.

Based on the Maya UI, embed the required PySide widget there.

Embed PySide in Maya UI

So how do you embed PySide in the Maya UI?

Even if you embed it, you just have to create a parent-child relationship.

In other words, we will specify the Maya UI as the parent for the PySide widget.

python


QWidget.setParent(parentWidget)

It's easy.

But here's the problem. The type to put in parentWidget must be PySide.QtGui.QWidget </ font>.

python


QWidget.setParent(mayaUI)

Even so, it is no good </ font>.

Therefore, convert Maya UI to PySide once.

How to get Maya UI as PySide !! https://kiwamiden.com/how-to-get-mayas-ui-as-pyside

There was! An article written by ** Takkun Dai-sensei **!

Good job!

That's why it's a solution.

Since it is PySide2 in Maya 2017 or later, I will rewrite it a little.

It's almost like a reprint, so I can't stand ** Dai-sensei ** ...

python


from PySide2 import QtGui
from PySide2 import QtWidgets
try:
    import shiboken2
except ImportError:
    from PySide2 import shiboken2
import maya.OpenMayaUI as omUI

# ---------------------------------------------------------
# mayaUI to pyside(thanks to takkyun)
# ---------------------------------------------------------
# @param <str>name    : maya UI name
# @param <type>toType : pyside type
# @return <obj> : pyside obj
def mayaToPySide(name, toType):

    ptr = omUI.MQtUtil.findControl(name)
    if not ptr:
        ptr = omUI.MQtUtil.findLayout(name)    
    if not ptr:
        ptr = omUI.MQtUtil.findMenuItem(name)
    if not ptr:
        return None
 
    return shiboken2.wrapInstance(long(ptr), toType)

This ...

python


QWidget.setParent(mayaToPySide(mayaUI), QtWidgets.QWidget)

Now you can create a parent-child relationship = embed PySide.

Try to make a sample

First, create a Maya UI that will serve as a container.

Here is what I made for 3 minutes cooking.

python


import maya.cmds as cmds
class toolGUI(object):
    windowName = 'coordWindow'
    windowTitle = 'Coordinate window'
    tabName = ['Coordinate']
    icName = 'coord'
    defWidth = 510
    defHeight = 530

    # ---------------------------------------------------------
    # init
    # ---------------------------------------------------------
    # @param None
    # @return None
    def __init__(self):
        if cmds.window(self.windowName, exists=True):
            cmds.deleteUI(self.windowName)

    # ---------------------------------------------------------
    # UI : Tab layout
    # ---------------------------------------------------------
    # @param None
    # @return <uiObj>coordTabcLayout : tab layout
    def tab_coord(self):
        coordTabcLayout = cmds.columnLayout(adj=1, p=self.alltabLayout)
        # thumbnail UI

        return coordTabcLayout

    # ---------------------------------------------------------
    # UI : show window
    # ---------------------------------------------------------
    # @param None
    # @return None
    def show(self):
        windowlayout = cmds.window(self.windowName, title=self.windowTitle,
                                   iconName=self.icName, menuBar=True)
        # window layout
        allformLayout = cmds.formLayout()
        self.alltabLayout = cmds.tabLayout(p=allformLayout)
        # build coord Tab
        coordTabcLayout = self.tab_coord()
        # set tab
        cmds.tabLayout(self.alltabLayout, e=True,
                        tabLabel=((coordTabcLayout, self.tabName[0])))
        # all form
        cmds.formLayout(allformLayout, e=True, af=[self.alltabLayout, 'top', 0])
        cmds.formLayout(allformLayout, e=True, af=[self.alltabLayout, 'left', 0])
        cmds.formLayout(allformLayout, e=True, af=[self.alltabLayout, 'right', 0])
        cmds.formLayout(allformLayout, e=True, af=[self.alltabLayout, 'bottom', 0])
        cmds.setParent('..')
        cmds.showWindow()

        cmds.window(self.windowName, e=True, w=self.defWidth, h=self.defHeight)

Current situation here

python


gui = toolGUI()
gui.show()

When you run it with this coordUI_00.png Like this.

Next, place the UI for displaying the image at ** # thumbnail UI ** in the code above.

Use QtWidgets.QGraphicsView </ font> to display images in PySide.

There is another way to just display the image, but to make it easier to control the display.

To draw something in QGraphicsView, specify QtWidgets.QGraphicsScene </ font> and put the object to be drawn in it.

This time we will draw an image, so we will use QtGui.QPixmap </ font>.

In Maya, QGraphicsView is a project, QGraphicsScene is scene data, and QPixmap is a texture.

I think it's actually different, but the general inclusion relationship is like that.

so,

Create QGraphicsView
 ↓ 
Create QGraphicsScene
 ↓ 
QPixmap creation
 ↓
Set QPixmap to QGraphicsScene
 ↓
Set QGraphicsScene in QGraphicsView

I will do it.

QPixmap creates a QPixmap object for that image by setting the path of the image when it is created.

python


QtGui.QPixmap('C:/img/test.png')

After that, it's okay if you make the parent of QGraphicsView the layout UI of Maya.

At this time, it should be noted that the adjustment flags existing in the Maya layout do not work.

Therefore, if you just throw QGraphicsView into the layout, coordUI_01.png You'll only see a view of just a stick like this.

It's confusing, but the light blue line below the Coordinate text is the view.

So, I will specify the size of the layout arbitrarily.

At this time, specify the size of QGraphicsView as well.

By doing the above, you will be able to display the image.

When it is actually made into a function, it looks like the one below.

python


    # ---------------------------------------------------------
    # UI : coord frame layout
    # ---------------------------------------------------------
    # @param <uiObj>parent : parent UI
    # @return <uiObj>coordformLayout : form layout
    def frame_coord(self, parent):
        coordformLayout = cmds.formLayout(p=parent)
        # thumbnail row
        self.coordThumbnailLayout = cmds.columnLayout(adj=True, p=coordformLayout)
        thumbLayoutPyside = mayaToPySide(self.coordThumbnailLayout, QtWidgets.QWidget)
        self.coordGView = QtWidgets.QGraphicsView()
        self.coordGView.setParent(thumbLayoutPyside)
        self.coordThumbScene = QtWidgets.QGraphicsScene()
        self.coordThumbScene.clear()
        self.coordThumbScene.addPixmap(QtGui.QPixmap('C:/img/test.png'))
        self.coordGView.setScene(self.coordThumbScene)
        cmds.setParent(coordformLayout)
        # size 
        self.coordGView.resize(self.thumbWidth, self.thumbHeight)
        cmds.columnLayout(self.coordThumbnailLayout, e=True, w=self.thumbWidth, h=self.thumbHeight)

        # coord frame formLayout
        cmds.formLayout(coordformLayout, e=True, ap=[self.coordThumbnailLayout, 'top', 0, 0])
        cmds.formLayout(coordformLayout, e=True, af=[self.coordThumbnailLayout, 'left', 0])
        cmds.formLayout(coordformLayout, e=True, af=[self.coordThumbnailLayout, 'right', 0])
        cmds.setParent('..')

The layout with PySide is added to the parent layout.

By creating a layout for PySide, it will be easier to control with Maya UI commands.

The parent layout is made easier to lay out by including it in this parent layout when creating another UI such as optionMenu </ font>.

After that, I incorporated this function into the base of Maya UI that I made earlier,

If you write self.frame_coord (coordTabcLayout) </ font> in the ** \ # thumbnail UI ** part, a sample will be displayed.

Den ☆ coordUI_02.png

Try to make something that looks like character makeup

Well, it's a little later when we get here.

What we are doing in the sample at the beginning is just overlaying the png transparent image.

In other words, create as many QPixmaps as you need and register them in the QGraphicsScene.

What we need later is a UI for input and a mechanism to change the image when inputting.

The UI for input is omitted because it only adds optionMenu </ font>.

Make a optionMenu </ font> and place it in your existing layout.

Next is the mechanism for changing the image.

After creating an image object with QPixmap,

pixmap.size (). Width () </ font> and pixmap.size (). Height () </ font> can be used for vertical and horizontal sizes. ..

Use this guy to flexibly make it the same size.

This time, instead of making the image itself smaller,

I tried to control the size by scaling the QGraphicsView.

python


QGraphicsView.scale(scaleVal, scaleVal)

The rest is the replacement of the image.

To replace it, replace the QPixmap.

QGraphicsPixmapItem is registered in QGraphicsScene when QPixmap is set.

Earlier, I said that QPixmap is a texture if you compare it to Maya.

Textures alone cannot be displayed in Maya views.

You need an object to texture.

The object for pasting the texture is QGraphicsPixmapItem.

In other words, specifically, you can replace the QPixmap attached to the QGraphicsPixmapItem.

Get the QGraphicsItem from the QGraphicsScene and pick up only the QGraphicsPixmapItem in it.

python


    # ---------------------------------------------------------
    # thumbnail : get imageItem from scene
    # ---------------------------------------------------------
    # @param <QtWidgets.QGraphicsScene>scene : scene object
    # @return <QtWidgets.QGraphicsPixmapItem/List>bgimgList : image object
    def getImgItemFromScn(self, scene):
        bgimgList = []
        for item in scene.items():
            if type(item) == QtWidgets.QGraphicsPixmapItem:
                bgimgList.append(item)
        return bgimgList

It looks like this when it is made into a function.

It looks like this to set the QPixmap.

python


QGraphicsPixmapItem.setPixmap(pixmap)

The image of each part is changed while overlapping with this,

What you have to pay attention to here is the order of superimposition.

When you get the QGraphicsItem from the QGraphicsScene, the displayed items are fetched in order from the top.

In other words, in this sample, the QGraphicsPixmapItem that is displayed in the order of head, body, legs, and back hair is acquired.

After that, there is no problem if you replace the pixmap in consideration of the acquisition order of this.

This time, the display position of the image remains the same because the display position is adjusted on the image side.

When adjusting on the cord side, adjust the position separately.

The above is the mechanism for changing the image.

Make this function hit when the optionMenu </ font> switches.

I skipped the annoying image creation ...



And here is the finished product. coordUI_01.gif Cute yatter

I inherited QtWidgets.QGraphicsView </ font> and tried to make it possible to scale and move by moving the class.

That's why I used QGraphicsView-I see.

As for the scaling movement, the paper width (article width?) Is about to run out, and it will be a PySide story, so on another occasion.

Cute yatter

This was just 2D, but it would be even better if it could be a 3D viewer.

If you do so far, if you do it all with PySide ... Gefungefun

No, you can use PySide's UI module for your existing Maya UI!

Can be used Can be used

I think there are other better ways to embed it, but for the time being, I tried it like this.

I hope it helps when you want to add some effort to the Maya UI.

By the way, it was harder to make an image for the sample than to write the honest code.

70% of this article is made up of images for sample code.

So far (: 3 noshi)

Tomorrow is the story of "Registering commands to the main menu using decorator" by yamahigashi @ github.

UCL_logo.png

    • Disclaimer * We do not guarantee the security of the code published in this article. We are not responsible for any damage caused by using these codes. Please use at your own risk. *