Maya GUI with image map - arbitrary hitbox

Hi. Long time no see. But eventually find myself back here. Great community.
I was looking into building more complex UI:s in Maya using PySide2 or QML. What I’m looking for is to create something like this. Arbitrary shaped hitboxes.

Not really sure what he uses, but the type of things I’m interested in can be achieved in HTML. however I’m not sure if it is possible in Qt this way. There used to be HTML support in Maya, right? like back in v6? https://www.youtube.com/watch?v=prU2MYPMoiw

So I’m eager for anyones ideas or tips really.
Keep it up!

Qt does have the ability to render html, but I don’t know if those libraries are exposed to PySide (or if they were included in the Maya at all).

But using Qt, you should be able to acquire the coordinate in a Widget that the user has clicked, and filter down to a specific event.

Oh thank you. That was a super fast reply! :light_smile:
Yes, must have been someone who has done this. Can’t find any info on the subject though

Hey - look up QGraphicsView and QGraphicsScene.

The QGraphicsScene is a special kind of widget which allows you to paint items on a canvas and define how they draw and what actions occur when you interact with them etc.

Essetially you create a graphics scene, then you add items to the scene. Those items could be simple circles, rectangles etc - or they could be custom items which draw they own shapes using paths or images. Because items are classes, you can then override the mouse events, the paint events and most other things too.

As you progress you can utilise script jobs to make the panel bi-directional too, for instance in that video he clicks an item in the panel and it selects the control in the rig, it can be a nice polish feature to have the panel update live as you select things in the scene as well :slight_smile:

This is a very simple example of code:

That is doing the bare minimum to add something to the scene (the text item). Whilst this is a more thorough example:

Dont be put off by the amount of math in the second example, that can largely be ignored (because its creating a dynamic spring effect between the nodes). But it shows creating custom items and defining their draw behaviours etc.

Hope that helps a little - and please post any questions, we’re all more than happy to help!

1 Like

Hi. I just got back into this after a long time.
I’ve tested the idea with using the html approach. loading in a svg file, but obviously I don’t know how I could execute Maya code from a html-file and inject it back into Maya. Also I’m not sure if there’s a way to actually have a selection dragger functionality, selecting many islands at once. I was just inspired by the old tutorial about the html view that used to work with Maya.
https://imgur.com/9rXmha0

So I’ve started looking into using the QGraphicsScene because I’m sure it could do exactly what I want. I’ve found some resources that got me started. Namely this one.

And here’s an example. I can upload the images I used, otherwise I got one online which i’m using for both the image and the mask. It works, but comes out with some artefacts.

# PySide2 selection ui with region masks

from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtGui import QPixmap, QIcon
import urllib

class QLabelButton(QtWidgets.QLabel):

    def __init(self, parent):
        QtWidgets.QLabel.__init__(self, parent)

    def mousePressEvent(self, ev):
        self.emit(QtCore.SIGNAL('clicked()'))

class CustomButton(QtWidgets.QWidget):
    def __init__(self, parent=None, *args):
        super(CustomButton, self).__init__(parent)
        self.setMinimumSize(512, 512)
        self.setMaximumSize(768, 768)


        
        #------ online image
        url = 'https://www.nicepng.com/png/detail/131-1312454_open-flower-icon.png'
        data = urllib.urlopen(url).read()
        pixmap = QPixmap()
        pixmap.loadFromData(data)
        pixmap = pixmap.scaled(500, 500, QtCore.Qt.KeepAspectRatio) 
        icon = QIcon(pixmap)

        self.button = QLabelButton(self)
        self.button.setPixmap(QtGui.QPixmap(pixmap))
        self.button.setMask(QtGui.QPixmap(pixmap))
        #-------
        
        
        #------- local image
        '''
        picture = "H://03_personal_files//20_MAYA_CUSTOM_CONFIG//11_MODULES_PACK_EVALUATION//PySide2_UI_mask//mario.png"
        mask = "H://03_personal_files//20_MAYA_CUSTOM_CONFIG//11_MODULES_PACK_EVALUATION//PySide2_UI_mask//mario_mask.png"
        self.button = QLabelButton(self)
        self.button.setPixmap(QtGui.QPixmap(picture))
        self.button.setMask(mask)
        '''
        #-------
        
        
        self.button.setScaledContents(True)
        self.connect(self.button, QtCore.SIGNAL('clicked()'), self.onClick)

    def onClick(self):
        print('Button was clicked')


if __name__ == '__main__':

    app = QtWidgets.QApplication.instance()
    if app == None:
        app = QtWidgets.QApplication([])
    
    win = CustomButton()
    win.show()
    app.exec_()
    sys.exit()

However I’m looking to have many masks to split up a character in segments and I’m not entirely sure how to do it. I then found this link: Qt achieve irregular button - Code World

and this is precisely what I would like to do. paint different region masks to act as buttons. I’m sure it’s possible to implement selection dragging over multiple buttons too.

And thanks so much if I forgot to earlier. I tested this UI too. I guess there’s a lot to learn in there.

So I’ve been working on this and trying to add a rect drag selection. Now I’m stuck at getting said selection to only select the parts where I have painted masks. Unfortunately it sees the QLabelButton, not filtering to its mask and because the QLabelButton is as big as the window, it will always be selected no matter where I drag and release.

I’ve posted on stack overflow too. If this is an illegal cross-post I’ll delete it. But I could use any help or ideas.

resources

from maya import OpenMayaUI
import pymel.core as pm
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtGui import QPixmap, QIcon
from shiboken2 import wrapInstance
import urllib

class QLabelButton(QtWidgets.QLabel):
      

    def mouseReleaseEvent(self, ev):
        self.emit(QtCore.SIGNAL('clicked()'))

    

def main_maya_window():
    main_window_ptr = OpenMayaUI.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)
    
    
class rubberBandUI(QtWidgets.QDialog):

    def __init__(self, parent=main_maya_window()):
        super(rubberBandUI, self).__init__(parent)
        imgRes = [512, 512]
        self.setMinimumSize(imgRes[0], imgRes[1])
        self.setMaximumSize(imgRes[0], imgRes[1])
        

        # resources
        bgPath = r"C://bg.png"
        maskPath = r"C://bg_mask.png"

        # background image
        self.bg = QtWidgets.QLabel('bg', self)
        self.bg.setPixmap(QtGui.QPixmap(bgPath))

        # mask image
        self.mask = QLabelButton('mask', self)
        self.mask.setGeometry(0, 0, imgRes[0], imgRes[1])
        self.mask.setAlignment(QtCore.Qt.AlignCenter)
        self.mask.setMask(maskPath)
        self.connect(self.mask, QtCore.SIGNAL('clicked()'), lambda: self.onClick(self.mask) )
        self.mask.setStyleSheet("""QLabel:hover{background-color: rgba(64, 128, 255, 180); border: 1px solid blue;}""")









        # create rubber band selection
        self.rubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)

        # Create variables that will be handling the moving logic.
        self.sourceGeo = None
        self.altPoint = None
        self.delta = None


    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            
            self.origin = event.pos()
            self.drag_selection = QtCore.QRect(self.origin, QtCore.QSize())
            self.rubberBand.setGeometry(self.drag_selection)
            self.rubberBand.show()

    # Must implement this event to detect when alt gets released
    def keyReleaseEvent(self, event):
        if event.key() == QtCore.Qt.Key_Alt:
            if self.delta is not None:
                # This is important: Add delta to origin so that it shifts it over.
                # This is needed if the user repeatedly pressed alt to move it, otherwise it would pop.
                self.origin += self.delta

                # Reset the rest of the variables.
                self.sourceGeo = None
                self.altPoint = None
                self.delta = None


    def mouseMoveEvent(self, event):
        if event.modifiers() == QtCore.Qt.AltModifier:
            # Get the point where alt is pressed and the selection's current geometry.
            if self.altPoint is None:
                self.sourceGeo = self.rubberBand.geometry()
                self.altPoint = event.pos()

            self.delta = event.pos() - self.altPoint  # Calculate difference from the point alt was pressed to where the cursor is now.
            newGeo = QtCore.QRect(self.sourceGeo)  # Create a copy
            newGeo.moveTopLeft(self.sourceGeo.topLeft() + self.delta)  # Apply the delta onto the geometry to move it.
            self.rubberBand.setGeometry(newGeo)  # Move the selection!
        else:
            self.drag_selection = QtCore.QRect(self.origin, event.pos()).normalized()
            self.rubberBand.setGeometry(self.drag_selection)

    def mouseReleaseEvent(self, event):
        if self.rubberBand.isVisible():
            self.rubberBand.hide()
            selected = []
            rect = self.rubberBand.geometry()
            #self.cropImage(rect)
            
            for child in self.findChildren(QLabelButton):
                if rect.intersects(child.geometry()):
                    selected.append(child)
            print 'Selection Contains:\n ',
            if selected:
                print '  '.join(
                    'Button: %s\n' % child.text() for child in selected)
            else:
                print ' Nothing\n'






    # label click function to print its name
    def onClick(self, button):
        print '%s clicked' % button.text()





if __name__ == '__main__':

    app = QtWidgets.QApplication.instance()
    if app == None:
        app = QtWidgets.QApplication([])
    win = rubberBandUI()
    #Set WA_DeleteOnClose attribute
    win.setAttribute(QtCore.Qt.WA_DeleteOnClose) 
    
    win.show()
    app.exec_()
    sys.exit()