New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement wrapInstance and getCppPointer (#59) #190

Merged
merged 7 commits into from Jun 27, 2017

Conversation

Projects
None yet
3 participants
@mottosso
Owner

mottosso commented Mar 29, 2017

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!

@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso Mar 29, 2017

Owner

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.

Store the .mel file in your $HOME/maya/scripts/AETemplates directory, or in any directory on your MAYA_SCRIPT_PATH

Notes

  • For whatever reason, templates can be in both XML and MEL format; only XML is documented (?), but mostly MEL is found in the Maya installation directory (e.g. Maya2018\scripts\AETemplates).
  • MAYA_CUSTOM_TEMPLATE_PATH is the documented variable for templates, which doesn't seem to have any effect. MAYA_SCRIPT_PATH on the other hand does.

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
Owner

mottosso commented Mar 29, 2017

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.

Store the .mel file in your $HOME/maya/scripts/AETemplates directory, or in any directory on your MAYA_SCRIPT_PATH

Notes

  • For whatever reason, templates can be in both XML and MEL format; only XML is documented (?), but mostly MEL is found in the Maya installation directory (e.g. Maya2018\scripts\AETemplates).
  • MAYA_CUSTOM_TEMPLATE_PATH is the documented variable for templates, which doesn't seem to have any effect. MAYA_SCRIPT_PATH on the other hand does.

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
@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso Mar 31, 2017

Owner

What are our thoughts on this?

He isn't pointing out what the difference is exactly, anyone experienced this problem?

Owner

mottosso commented Mar 31, 2017

What are our thoughts on this?

He isn't pointing out what the difference is exactly, anyone experienced this problem?

@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso Apr 7, 2017

Owner

He isn't pointing out what the difference is exactly, anyone experienced this problem?

His code isn't true. sip does not support what he's implemented for shiboken.

>>> import sip
>>> import sys
>>> from PyQt4 import QtGui
>>> app = QtGui.QApplication(sys.argv)
>>> button = QtGui.QPushButton("Hello world")
>>> pointer = sip.unwrapinstance(button)
>>> sip.wrapinstance(long(pointer))
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: wrapinstance() takes exactly 2 arguments (1 given)
>>> sip.wrapinstance(long(pointer), None)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: must be sip.wrappertype, not None

I've implemented this regardless, as I can't see why not and it makes for less code. The feature is ignored when used with a base argument and kicks in automatically when ignoring it.

import sys
from Qt import QtCompat, QtWidgets, __binding__

app = QtWidgets.QApplication(sys.argv)

try:
    button = QtWidgets.QPushButton("Hello world")
    button.setObjectName("MySpecialButton")
    pointer = QtCompat.getCppPointer(button)
    widget = QtCompat.wrapInstance(long(pointer))
    assert isinstance(widget, QtWidgets.QWidget), widget
    assert widget.objectName() == button.objectName()

    if __binding__ in ("PySide", "PySide2"):
        assert widget != button
    else:
        assert widget == button
finally:
    app.exit()

In either case, I uncovered a subtle difference between sip and shiboken whilst writing the tests.
Notice, the objects are not directly comparable in PySide 1 and 2, but are in PyQt4 and PyQt5.

Owner

mottosso commented Apr 7, 2017

He isn't pointing out what the difference is exactly, anyone experienced this problem?

His code isn't true. sip does not support what he's implemented for shiboken.

>>> import sip
>>> import sys
>>> from PyQt4 import QtGui
>>> app = QtGui.QApplication(sys.argv)
>>> button = QtGui.QPushButton("Hello world")
>>> pointer = sip.unwrapinstance(button)
>>> sip.wrapinstance(long(pointer))
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: wrapinstance() takes exactly 2 arguments (1 given)
>>> sip.wrapinstance(long(pointer), None)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: must be sip.wrappertype, not None

I've implemented this regardless, as I can't see why not and it makes for less code. The feature is ignored when used with a base argument and kicks in automatically when ignoring it.

import sys
from Qt import QtCompat, QtWidgets, __binding__

app = QtWidgets.QApplication(sys.argv)

try:
    button = QtWidgets.QPushButton("Hello world")
    button.setObjectName("MySpecialButton")
    pointer = QtCompat.getCppPointer(button)
    widget = QtCompat.wrapInstance(long(pointer))
    assert isinstance(widget, QtWidgets.QWidget), widget
    assert widget.objectName() == button.objectName()

    if __binding__ in ("PySide", "PySide2"):
        assert widget != button
    else:
        assert widget == button
finally:
    app.exit()

In either case, I uncovered a subtle difference between sip and shiboken whilst writing the tests.
Notice, the objects are not directly comparable in PySide 1 and 2, but are in PyQt4 and PyQt5.

@dgovil

This comment has been minimized.

Show comment
Hide comment
@dgovil

dgovil May 18, 2017

Contributor

Hi @mottosso I just wanted to check what the current state of this PR was in your view? What still needs doing?

I was considering merging this into our local copy of Qt.py till the PR was merged unless you think there will be major changes?

Contributor

dgovil commented May 18, 2017

Hi @mottosso I just wanted to check what the current state of this PR was in your view? What still needs doing?

I was considering merging this into our local copy of Qt.py till the PR was merged unless you think there will be major changes?

@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso May 18, 2017

Owner

Hey @dgovil, no major changes as far as I can see, what's missing now is testing.

I was considering merging this into our local copy of Qt.py

Please do, after some use, if you haven't encountered any issues, that'd be a good point to merge this.

Owner

mottosso commented May 18, 2017

Hey @dgovil, no major changes as far as I can see, what's missing now is testing.

I was considering merging this into our local copy of Qt.py

Please do, after some use, if you haven't encountered any issues, that'd be a good point to merge this.

"""
assert isinstance(ptr, long), "Argument 'ptr' must be of type <long>"

This comment has been minimized.

@dgovil

dgovil May 18, 2017

Contributor

As far as I know, in Python3 there is no longer a long, and instead it should be an int in python3.

@dgovil

dgovil May 18, 2017

Contributor

As far as I know, in Python3 there is no longer a long, and instead it should be an int in python3.

This comment has been minimized.

@mottosso

mottosso May 18, 2017

Owner

Hm, you're right. This wasn't caught by Travis, I'll take a closer look as to why that is tomorrow.

@mottosso

mottosso May 18, 2017

Owner

Hm, you're right. This wasn't caught by Travis, I'll take a closer look as to why that is tomorrow.

This comment has been minimized.

@mottosso

mottosso May 27, 2017

Owner

Ok, this has been fixed.

Turns out shiboken isn't supported by Python 3.5.
pyside/shiboken-setup#3

@mottosso

mottosso May 27, 2017

Owner

Ok, this has been fixed.

Turns out shiboken isn't supported by Python 3.5.
pyside/shiboken-setup#3

@mottosso mottosso changed the title from Implement #59 to Implement wrapInstance and getCppPointer (#59) May 22, 2017

@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso May 22, 2017

Owner

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.
Owner

mottosso commented May 22, 2017

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.

mottosso added some commits May 27, 2017

@dgovil

This comment has been minimized.

Show comment
Hide comment
@dgovil

dgovil May 29, 2017

Contributor

FWIW, been running this in production for a week and it seems to be working correctly after porting some existing tools over to using this.

Contributor

dgovil commented May 29, 2017

FWIW, been running this in production for a week and it seems to be working correctly after porting some existing tools over to using this.

@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso May 29, 2017

Owner

That's great @dgovil, thanks for reporting back.

Owner

mottosso commented May 29, 2017

That's great @dgovil, thanks for reporting back.

ptr (long): Pointer to QObject in memory
base (QObject, optional): Base class to wrap with. Defaults to QObject,
which should handle anything.

This comment has been minimized.

@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

Perhaps we should add a "Returns:" to the docstring to make it easier to understand the different types of return values are possible here, as I'm guessing it's not always a QObject?

@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

Perhaps we should add a "Returns:" to the docstring to make it easier to understand the different types of return values are possible here, as I'm guessing it's not always a QObject?

This comment has been minimized.

@mottosso

mottosso May 30, 2017

Owner

Yes, and long must also change.

Not sure how to tackle the fact that long is absent from Python 3. I had a look at us internally taking int for both Python 2 and 3, but then we stray from the original PySide2.wrapInstance argument signature.

@mottosso

mottosso May 30, 2017

Owner

Yes, and long must also change.

Not sure how to tackle the fact that long is absent from Python 3. I had a look at us internally taking int for both Python 2 and 3, but then we stray from the original PySide2.wrapInstance argument signature.

base = getattr(Qt.QtWidgets, super_class_name)
else:
base = Qt.QtCore.QObject

This comment has been minimized.

@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

Out of curiosity, in which scenario(s) will base be set to Qt.QtCore.QObject?
Would you mind adding comments in the if/else conditions to make this a bit more grokable?

@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

Out of curiosity, in which scenario(s) will base be set to Qt.QtCore.QObject?
Would you mind adding comments in the if/else conditions to make this a bit more grokable?

This comment has been minimized.

@mottosso

mottosso May 30, 2017

Owner

Likely never, all objects you could get from this function are subclasses of QObject. It's set as default here as a fallback, as anything, even a QWidget or your own subclass of a subclass of QMainWindow could be interpreted as QObject. In those cases, you still gain access to various superclass properties like objectName() but not the more specialised methods like resize() etc.

@mottosso

mottosso May 30, 2017

Owner

Likely never, all objects you could get from this function are subclasses of QObject. It's set as default here as a fallback, as anything, even a QWidget or your own subclass of a subclass of QMainWindow could be interpreted as QObject. In those cases, you still gain access to various superclass properties like objectName() but not the more specialised methods like resize() etc.

@fredrikaverpil

This comment has been minimized.

Show comment
Hide comment
@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

I'm sorry I've been so absent in regards to this addition to Qt.py.

This is a fantastic PR. Crossing my fingers we won't have to spend too much time maintaining this though. 😉

Before merging, I'd like to test this Qt.py version internally:

pip install git+https://github.com/abstractfactory/Qt.py.git@implement59

Really nice job, @mottosso and @dgovil!

Collaborator

fredrikaverpil commented May 30, 2017

I'm sorry I've been so absent in regards to this addition to Qt.py.

This is a fantastic PR. Crossing my fingers we won't have to spend too much time maintaining this though. 😉

Before merging, I'd like to test this Qt.py version internally:

pip install git+https://github.com/abstractfactory/Qt.py.git@implement59

Really nice job, @mottosso and @dgovil!

@fredrikaverpil
  • We should revise the loadUi examples.
  • (Don't forget to update the __version__)
@fredrikaverpil

This comment has been minimized.

Show comment
Hide comment
@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

I'll look into this but I'm getting a weird "QMainWindow already deleted" error in Nuke. The same code works fine with Qt.py 1.0.0b3:

# uisetup.py
class MyWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None, resize=[]):
        super(MyWindow, self).__init__(parent)
        main_window_file = os.path.join(ui_dir, 'main_window.ui')  # ui file is using the QMainWindow class
        self.main_widget = QtCompat.loadUi(main_window_file)  # errors as seen below
        ...
        ...

The class is called like this from within uisetup.py:

panel = nukescripts.panels.registerWidgetAsPanel(
    widget='uisetup.MyWindow',
    name='SomeWindow',
    id='uk.co.thefoundry.' + 'SomeWindow',
    create=True)

The error:

Traceback (most recent call last):
  File "C:/Users/[redacted]/bin/nuke/builds/nuke_v9.0v9_win64/plugins\nukescripts\panels.py", line 153, in makeUI
    self.widget = self.widgetClass()
  File "c:\users\iruser\code\repos\skalman\skalman\uisetup.py", line 154, in __init__
    self.main_widget = QtCompat.loadUi(main_window_file)
  File "C:\Users\iruser\skalman\condaenvs\skalman_py27\Lib\site-packages\Qt.py", line 1028, in _loadUi
    Qt.QtCore.QMetaObject.connectSlotsByName(widget)
RuntimeError: Internal C++ object (PySide.QtGui.QMainWindow) already deleted.
Collaborator

fredrikaverpil commented May 30, 2017

I'll look into this but I'm getting a weird "QMainWindow already deleted" error in Nuke. The same code works fine with Qt.py 1.0.0b3:

# uisetup.py
class MyWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None, resize=[]):
        super(MyWindow, self).__init__(parent)
        main_window_file = os.path.join(ui_dir, 'main_window.ui')  # ui file is using the QMainWindow class
        self.main_widget = QtCompat.loadUi(main_window_file)  # errors as seen below
        ...
        ...

The class is called like this from within uisetup.py:

panel = nukescripts.panels.registerWidgetAsPanel(
    widget='uisetup.MyWindow',
    name='SomeWindow',
    id='uk.co.thefoundry.' + 'SomeWindow',
    create=True)

The error:

Traceback (most recent call last):
  File "C:/Users/[redacted]/bin/nuke/builds/nuke_v9.0v9_win64/plugins\nukescripts\panels.py", line 153, in makeUI
    self.widget = self.widgetClass()
  File "c:\users\iruser\code\repos\skalman\skalman\uisetup.py", line 154, in __init__
    self.main_widget = QtCompat.loadUi(main_window_file)
  File "C:\Users\iruser\skalman\condaenvs\skalman_py27\Lib\site-packages\Qt.py", line 1028, in _loadUi
    Qt.QtCore.QMetaObject.connectSlotsByName(widget)
RuntimeError: Internal C++ object (PySide.QtGui.QMainWindow) already deleted.
@fredrikaverpil

This comment has been minimized.

Show comment
Hide comment
@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

Ok, here's a reproducible.

This runs just fine in Nuke 9.0v9 with Qt.py 1.0.0b3 but errors using Qt.py from this PR:

import tempfile
import os
import io

from Qt import QtWidgets
from Qt import QtCompat

import nuke
import nukescripts


TEMPDIR = tempfile.mkdtemp()
tempfile = os.path.join(TEMPDIR, "qmainwindow.ui")

with io.open(tempfile, "w", encoding="utf-8") as f:
    f.write(u"""\
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>223</width>
    <height>140</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>70</x>
      <y>40</y>
      <width>80</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>223</width>
     <height>17</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
""")


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.main_widget = QtCompat.loadUi(tempfile)
        self.setCentralWidget(self.main_widget)

# assert hasattr(Qt, '_wrapinstance')  # Make sure we are using abstractfactory:implement59

panel = nukescripts.panels.registerWidgetAsPanel(
    widget='MyWindow',
    name='SomeWindow',
    id='uk.co.thefoundry.' + 'SomeWindow',
    create=True)

pane = nuke.getPaneFor('Properties.1')
panel.addToPane(pane)
my_window_widget = panel.customKnob.getObject().widget

assert my_window_widget
Collaborator

fredrikaverpil commented May 30, 2017

Ok, here's a reproducible.

This runs just fine in Nuke 9.0v9 with Qt.py 1.0.0b3 but errors using Qt.py from this PR:

import tempfile
import os
import io

from Qt import QtWidgets
from Qt import QtCompat

import nuke
import nukescripts


TEMPDIR = tempfile.mkdtemp()
tempfile = os.path.join(TEMPDIR, "qmainwindow.ui")

with io.open(tempfile, "w", encoding="utf-8") as f:
    f.write(u"""\
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>223</width>
    <height>140</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>70</x>
      <y>40</y>
      <width>80</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>223</width>
     <height>17</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
""")


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.main_widget = QtCompat.loadUi(tempfile)
        self.setCentralWidget(self.main_widget)

# assert hasattr(Qt, '_wrapinstance')  # Make sure we are using abstractfactory:implement59

panel = nukescripts.panels.registerWidgetAsPanel(
    widget='MyWindow',
    name='SomeWindow',
    id='uk.co.thefoundry.' + 'SomeWindow',
    create=True)

pane = nuke.getPaneFor('Properties.1')
panel.addToPane(pane)
my_window_widget = panel.customKnob.getObject().widget

assert my_window_widget
@fredrikaverpil

This comment has been minimized.

Show comment
Hide comment
@fredrikaverpil
Collaborator

fredrikaverpil commented May 30, 2017

@fredrikaverpil

This comment has been minimized.

Show comment
Hide comment
@fredrikaverpil

fredrikaverpil May 30, 2017

Collaborator

Here's a slightly more portable piece of reproducible code which doesn't require Nuke.

This requires PySide.

import tempfile
import os
import io

from Qt import QtWidgets
from Qt import QtCompat

TEMPDIR = tempfile.mkdtemp()
tempfile = os.path.join(TEMPDIR, "qmainwindow.ui")

with io.open(tempfile, "w", encoding="utf-8") as f:
    f.write(u"""\
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>223</width>
    <height>140</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>70</x>
      <y>40</y>
      <width>80</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>223</width>
     <height>17</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
""")


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.main_widget = QtCompat.loadUi(tempfile)
        self.setCentralWidget(self.main_widget)

my_window = MyWindow()
my_window.show()
Collaborator

fredrikaverpil commented May 30, 2017

Here's a slightly more portable piece of reproducible code which doesn't require Nuke.

This requires PySide.

import tempfile
import os
import io

from Qt import QtWidgets
from Qt import QtCompat

TEMPDIR = tempfile.mkdtemp()
tempfile = os.path.join(TEMPDIR, "qmainwindow.ui")

with io.open(tempfile, "w", encoding="utf-8") as f:
    f.write(u"""\
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>223</width>
    <height>140</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>70</x>
      <y>40</y>
      <width>80</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>223</width>
     <height>17</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
""")


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.main_widget = QtCompat.loadUi(tempfile)
        self.setCentralWidget(self.main_widget)

my_window = MyWindow()
my_window.show()

Whoa, that was a bit unnecessary... first time testing the review features...

@dgovil

This comment has been minimized.

Show comment
Hide comment
@dgovil

dgovil May 30, 2017

Contributor

I can't replicate that error with the PySide2 version for Maya 2017, or PySide 1.2.2 (Qt 4.8.5) and or PyQt4.10.1 (Qt 4.8.5). Haven't tried with PyQt5 yet though.
They all show me a window with a PushButton, and the other ui elements are available.

Granted I'm trying your last example standalone without Nuke being involved.

Edit:
Tried under Nuke 10.0v4 as well for your last example with PySide 1.2.2 (Qt 4.8.5) without error.
Could be a bug in the PySide version shipped with Nuke 9?

Contributor

dgovil commented May 30, 2017

I can't replicate that error with the PySide2 version for Maya 2017, or PySide 1.2.2 (Qt 4.8.5) and or PyQt4.10.1 (Qt 4.8.5). Haven't tried with PyQt5 yet though.
They all show me a window with a PushButton, and the other ui elements are available.

Granted I'm trying your last example standalone without Nuke being involved.

Edit:
Tried under Nuke 10.0v4 as well for your last example with PySide 1.2.2 (Qt 4.8.5) without error.
Could be a bug in the PySide version shipped with Nuke 9?

@fredrikaverpil

This comment has been minimized.

Show comment
Hide comment
@fredrikaverpil

fredrikaverpil May 31, 2017

Collaborator

@dgovil The code snippet I gave works fine everywhere but in Nuke. Since this was introduced in a previous version (and not in this PR) I'll file a separate issue.

Maya

screen shot 2017-05-31 at 08 20 20

Nuke

screen shot 2017-05-31 at 08 20 47

Collaborator

fredrikaverpil commented May 31, 2017

@dgovil The code snippet I gave works fine everywhere but in Nuke. Since this was introduced in a previous version (and not in this PR) I'll file a separate issue.

Maya

screen shot 2017-05-31 at 08 20 20

Nuke

screen shot 2017-05-31 at 08 20 47

@dgovil

This comment has been minimized.

Show comment
Hide comment
@dgovil

dgovil Jun 25, 2017

Contributor

@mottosso I was wondering where wrapInstance ended up into making it into Qt.py? We've been using it in production for a while now without issue so I was wondering if it is possible to get it into Qt.py. Right now I am managing our own fork but I'd love to get it down to as few changes as possible

Contributor

dgovil commented Jun 25, 2017

@mottosso I was wondering where wrapInstance ended up into making it into Qt.py? We've been using it in production for a while now without issue so I was wondering if it is possible to get it into Qt.py. Right now I am managing our own fork but I'd love to get it down to as few changes as possible

@mottosso

This comment has been minimized.

Show comment
Hide comment
@mottosso

mottosso Jun 26, 2017

Owner

We've been using it in production for a while now without issue so I was wondering if it is possible to get it into Qt.py.

That's good enough for me.

If you could have a look at merging this with the current state of the repo then we should be able to have it merged by dinner time.

Owner

mottosso commented Jun 26, 2017

We've been using it in production for a while now without issue so I was wondering if it is possible to get it into Qt.py.

That's good enough for me.

If you could have a look at merging this with the current state of the repo then we should be able to have it merged by dinner time.

@mottosso mottosso merged commit 3095c3f into mottosso:master Jun 27, 2017

3 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
hound No violations found. Woof!
license/cla Contributor License Agreement is signed.
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment