Skip to content

Releases: mottosso/Qt.py

1.1.0.b2

27 Jul 16:17
Compare
Choose a tag to compare
1.1.0.b2 Pre-release
Pre-release

Maintenance release.

Install

To install a pre-release, prepend --pre to your pip install.

$ pip install --pre -U Qt.py

To upgrade an existing install, prepend -U as well.

$ pip install --pre Qt.py

1.1.0.b1

27 Jun 06:33
Compare
Choose a tag to compare
1.1.0.b1 Pre-release
Pre-release

This adds a wrapper for wrapInstance and getCppPointer from shiboken2 and automatically unifies the differences with shiboken and sip for both Python 2 and 3.

Attribute Returns
QtCompat.wrapInstance(addr=long, type=QObject) QObject
QtCompat.getCppPointer(object=QObject) long

Usage

import sys
from Qt import QtCompat, QtWidgets
app = QtWidgets.QApplication(sys.argv)
button = QtWidgets.QPushButton("Hello world")
pointer = QtCompat.getCppPointer(button)
widget = QtCompat.wrapInstance(long(pointer), QtWidgets.QWidget)
assert widget == button
app.exit()

Maya Example

This works for both 2016 and 2017.

import sys
from maya import OpenMayaUI
from Qt import QtCompat, QtWidgets
pointer = OpenMayaUI.MQtUtil.mainWindow()
widget = QtCompat.wrapInstance(long(pointer), QtWidgets.QWidget)
assert isinstance(widget, QtWidgets.QWidget)

Important

This addition requires sip, shiboken or shiboken2 to be available on your system. If not found, Qt.py will still import successfully, but these members will not be available.

In such cases, here is a Qt-only version and guaranteed cross-compatible version of the above.

from Qt import QtWidgets
app = QtWidgets.QApplication.instance()
widget = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]

The same pattern may be applied to any and all uses of sip, shiboken and shiboken2, as discussed in-depth at #53.

Enjoy!

More Examples

For consideration into the main README.

wrapInstance have found particular use in Autodesk Maya, below are a few scenarios in which it is commonly used along with cross-binding alternatives.


Finding Widget Through MEL

shiboken

from maya import mel, OpenMayaUI
from Qt import QtWidgets
import shiboken2

status_line = mel.eval('$temp=$gStatusLineForm')
ptr = OpenMayaUI.MQtUtil.findControl(status_line)
status_line = shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)
status_line = status_line.children()[1].children()[1]
status_line.setStyleSheet("QWidget {background: red}")

Qt

from maya import mel
from Qt import QtWidgets
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
status_line = mel.eval('$temp=$gStatusLineForm')
status_line = window.findChild(QtGui.QWidget, gStatusLine)
status_lne.setStyleSheet("QWidget {background: red}")

Finding Widget through Object Name

shiboken

import shiboken
import maya.OpenMayaUI as apiUI
from Qt import QtWidgets
channel_box = apiUI.MQtUtil.findControl("mainChannelBox")
channel_box = shiboken.wrapInstance(long(channel_box), QtWidgets.QTableView)
channel_box.setStyleSheet("QWidget {background: red}")

Qt

from Qt import QtWidgets
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
channel_box = window.findChild(QtWidgets.QTableView, "mainChannelBox")
channel_box.setStyleSheet("QWidget {background: green}")

Custom Attribute Editor Template

For testing purposes, we'll create a custom node and associate an attribute editor template with it. The modification of the resulting template via Qt is what differs between shiboken and Qt.

Boilerplate

These two files are identical and cross-compatible.

// AEMyNodeTemplate.mel

global proc AEMyNodeTemplate(string $nodeName)
{
    editorTemplate -beginScrollLayout;
    editorTemplate -beginLayout "My Attributes" -collapse 0;
        editorTemplate -callCustom "MyNode_build_ui" "MyNode_update_ui" $nodeName;
        editorTemplate -addControl "x";
        editorTemplate -addControl "y";
        editorTemplate -addControl "z";
    editorTemplate -endLayout;
    editorTemplate -addExtraControls;
    editorTemplate -endScrollLayout;
}

global proc MyNode_build_ui( string $nodeName )
{
    string $parent = `setParent -q`;
    python("import myNodeUi");
    python("myNodeUi.build_ui('" + $parent + "', '" + $nodeName + "')");
}

global proc MyNode_update_ui( string $nodeName )
{
    string $parent = `setParent -q`;
    python("myNodeUi.update_ui('" + $parent + "', '" + $nodeName + "')");
}
# myNode.py
from maya import OpenMaya, OpenMayaMPx

kPluginNodeName = "MyNode"
MyNodeId = OpenMaya.MTypeId(524286)


class MyNode(OpenMayaMPx.MPxNode):
    _x = OpenMaya.MObject()
    _y = OpenMaya.MObject()
    _z = OpenMaya.MObject()

    def __init__(self):
        OpenMayaMPx.MPxNode.__init__(self)

    def compute(self, plug, data_block):
        print("Computing..")


def MyNodeCreator():
    return OpenMayaMPx.asMPxPtr(MyNode())


def MyNodeInit():
    attr = OpenMaya.MFnNumericAttribute()
    MyNode._x = attr.create("x", "x", OpenMaya.MFnNumericData.kFloat, 0.0)
    attr.setKeyable(True)
    MyNode._y = attr.create("y", "y", OpenMaya.MFnNumericData.kFloat, 0.0)
    attr.setKeyable(True)
    MyNode._z = attr.create("z", "z", OpenMaya.MFnNumericData.kFloat, 0.0)
    attr.setKeyable(True)
    MyNode.addAttribute(MyNode._x)
    MyNode.addAttribute(MyNode._y)
    MyNode.addAttribute(MyNode._z)


def initializePlugin(mobject):
    mplugin = OpenMayaMPx.MFnPlugin(mobject)
    mplugin.registerNode(
        kPluginNodeName,
        MyNodeId,
        MyNodeCreator,
        MyNodeInit,
        OpenMayaMPx.MPxNode.kDependNode
    )


def uninitializePlugin(mobject):
    mplugin = OpenMayaMPx.MFnPlugin(mobject)
    mplugin.deregisterNode(MyNodeId)

shiboken

Notice the OpenMayaUI and shiboken dependency.

# myNodeUi.py
from maya import cmds, OpenMayaUI

from Qt import QtWidgets

if cmds.about(api=True) >= 201700:
    from shiboken2 import wrapInstance
else:
    from shiboken import wrapInstance

def build_ui(layout, node):
    layout_ptr = OpenMayaUI.MQtUtil.findLayout(layout)
    layout_obj = wrapInstance(long(layout_ptr), QtWidgets.QWidget)
    layout_wid = layout_obj.findChild(QtWidgets.QBoxLayout)  # Cast to QBoxLayout

    widget = QtWidgets.QLabel("Hello World")
    layout_wid.insertWidget(0, widget)

def update_ui(layout, node):
    pass

Qt

# myNodeUi.py
from Qt import QtWidgets

def build_ui(layout, node):
    app = QtWidgets.QApplication.instance()
    window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]

    parent = window
    for child in layout.split("|")[1:]:
        parent = parent.findChild(QtWidgets.QWidget, child)

    widget = QtWidgets.QLabel("Hello World")
    layout = parent.findChild(QtWidgets.QBoxLayout)  # Cast to QBoxLayout
    layout.insertWidget(0, widget)

def update_ui(layout, node):
    pass

Get top-level window in any binding and any application.

sip and shiboken is sometimes used to fetch the main window of an application in order to make it a parent of a custom window. Below is an example of how to find said window efficiently and in any situation.

from Qt import QtWidgets

current = QtWidgets.QApplication.activeWindow()
while current:
    parent = current
    current = parent.parent()

print(parent)

Limitations

  • If run from within an already custom window that did not have it's parent set to the main window or a descendant of it, then this will return the custom window and may exit when it exists.

1.0.0

21 Jun 15:58
Compare
Choose a tag to compare

Stable release of 1.0!

This BACKWARDS INCOMPATIBLE version boasts quite a few additions and changes.

  • Enforce PySide2 API | Read more here and here
  • Optional Submodules, now you can use Qt.py on distribution missing common members, such as in Houdini, along with QML, OpenGL and third-party additions like Qscintilla | Read more
  • Improved consistency, load_ui() is now called loadUi(), just like its original | Read more
  • Support for baseinstance in loadUi() | Read more
  • Binding constants reap the benefits of static checking from your IDE | Read more
  • QtSiteConfig.py for detailed customisation of member availability | Read more

Enjoy!

1.0.0.b6

01 Jun 15:56
Compare
Choose a tag to compare
1.0.0.b6 Pre-release
Pre-release

QtSiteConfig.py

Individual members of Qt.py may now be customised via an additional module called QtSiteConfig.py. For example, one could remove QtCore if for whatever reason it shouldn't be used at all, or add site-specific modules like Qsci.

Thanks to @MHendricks for this feature!

1.0.0.b5

27 May 22:28
Compare
Choose a tag to compare
1.0.0.b5 Pre-release
Pre-release

Added support for binding constants.

import Qt

# Before
if Qt.__binding__ == "PyQt5":
  # Do PyQt5 things

# After
if Qt.IsPyQt5:
  # Do PyQt5 things

Which in addition to cutting down on typed characters also enables your IDE to detect potential misspellings. The previous method still exists and continues to work, there are no plans to deprecate it.

Thanks to @dgovil for this feature!

1.0.0.b4

17 May 14:48
Compare
Choose a tag to compare
1.0.0.b4 Pre-release
Pre-release

Added support for baseinstance to QtCompat.loadUi().

QtCompat.loadUi(uifile="my.ui", baseinstance=QtWidgets.QWidget)

This feature mimics the functionality (warts and all) of PyQt5.uic.loadUi found here.

Thanks to @dgovil for this feature!

1.0.0.b3

23 Mar 14:07
Compare
Choose a tag to compare
1.0.0.b3 Pre-release
Pre-release

Beta release of #179.

# Before
from Qt import QtCompat
QtCompat.load_ui(...)

# After
from Qt import QtCompat
QtCompat.loadUi(...)

This is backwards compatible, QtCompat.load_ui will continue to be available but is to be considered deprecated.

See #177

1.0.0.b2

22 Mar 15:50
Compare
Choose a tag to compare
1.0.0.b2 Pre-release
Pre-release

Beta release of #185.

  • See #186 for details.

1.0.0.b1

22 Mar 14:35
Compare
Choose a tag to compare
1.0.0.b1 Pre-release
Pre-release

Beta release of #152, an enforced PySide2 API.

  • See #173 for more details.

0.6.9

16 Dec 09:20
Compare
Choose a tag to compare

Maintenance release, it fixes the internal QT_TESTING environment variable such that members are properly tested during testing on Travis CI.

Thanks to @sol-ansano-kim for the fix!