-
Notifications
You must be signed in to change notification settings - Fork 253
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
Should there be a wrapper around sip/shiboken? #53
Comments
Where? :O |
Over at the Maya beta forums, but now I see he's interacting here. Ping @pipoun |
@pipoun - could you please provide a use case for this request/question? |
from Qt import QtGui, binder
import maya.OpenMayaUI
def get_maya_mainwindow():
"""Get the main Maya window
:return: :class:`QtGui.QMainWindow` instance of the top level Maya window
"""
ptr = maya.OpenMayaUI.MQtUtil.mainWindow()
if ptr is not None:
# wrap_instance (pep8), wrapInstance (shiboken) or wrapinstance (sip)
return binder.wrap_instance(long(ptr), QtGui.QMainWindow) |
A portable approach would be preferable. However..
This returns the script editor for me. from Qt import QtWidgets
window = QtWidgets.QApplication.activeWindow().objectName()
print(window) Unless I have another window active, in which case that window is returned. There is this. from Qt import QtWidgets
window = {w.objectName(): w for w in QtWidgets.qApp.topLevelWidgets()}["MayaWindow"]
print(window) |
I just wanted to focus on the fact that
There are other use cases than getting a main window as a
|
Thanks for the added info @pipoun. Would it be possible to show us some more examples? I haven't used this particular feature myself, so it's difficult to know what and how to wrap. |
To me, it sounds like there's a need for a convenience function " def wrapinstance(ptr, base=None):
"""
Utility to convert a pointer to a Qt class instance (PySide/PyQt compatible)
:param ptr: Pointer to QObject in memory
:type ptr: long or Swig instance
:param base: (Optional) Base class to wrap with (Defaults to QObject, which should handle anything)
:type base: QtGui.QWidget
:return: QWidget or subclass instance
:rtype: QtGui.QWidget
"""
if ptr is None:
return None
ptr = long(ptr) #Ensure type
if globals().has_key('shiboken'):
if base is None:
qObj = shiboken.wrapInstance(long(ptr), QtCore.QObject)
metaObj = qObj.metaObject()
cls = metaObj.className()
superCls = metaObj.superClass().className()
if hasattr(QtGui, cls):
base = getattr(QtGui, cls)
elif hasattr(QtGui, superCls):
base = getattr(QtGui, superCls)
else:
base = QtGui.QWidget
return shiboken.wrapInstance(long(ptr), base)
elif globals().has_key('sip'):
base = QtCore.QObject
return sip.wrapinstance(long(ptr), base)
else:
return None I've personally only used this to successfully fetch the Maya main window: def maya_main_window():
'"""Returns the main Maya window"""
main_window_ptr = omui.MQtUtil.mainWindow()
return wrapinstance(long(main_window_ptr), QtGui.QWidget) But for this particular thing, your code is much much nicer, @mottosso. I second @mottosso's request to see additional use cases. |
Yeah, a feature, encompassing 14% of the entire codebase (32/230 LOC), for a single usecase isn't worth it. Once we find more usecases, I'd be happy to reconsider this. |
Sometimes it's useful to access sip.delete() / shiboken.delete(), especially when redefining layouts, selection models, or running complex unit tests. Admittedly it's only really needed for PyQt4 but it would be useful to abstract this as something like C_binding. |
Hi @innerhippy, Would it be possible to share an example of where sip could be useful? That would really help determine how to best see it realised. |
Just a suggestion, would this work for you then? if "PyQt4" in QtCompat.__binding__:
sip.delete()
shiboken.delete() |
What I'm interested in, is whether there is an alternative to using |
Thanks @fredrikaverpil , that would certainly work and similar to what I was thinking. I guess the consideration is if this should be provided by the Qt module itself. We use it to delete layouts and for some unit tests where the destruction order of models gets a bit messed up (in PyQt4 anyway). I agree that ideally this shouldn't be necessary, and maybe refactoring the code would solve the problem, but sometimes it's a question of having to just making stuff work. I'll post some code snippets on Monday but have to get clearance first. |
Just wanted to add another voice here. I found this request when looking at porting our stack over to Maya2017 and doing the Qt4->Qt5 shuffle. We have upwards of 20 projects which use wrapinstance - our previous (inhouse) PyQt4/PySide shim provided access to the underlying binding where we used a couple of very specific methods, e.g. wrapInstance, delete, signature/methodsignature where behaviour was comparable. Common with others on this thread the largest use is wrapInstance generally against the Maya API: We could ask people to port to pymel: uitypes.py within Maya-specific code, which is already providing this switching logic. But the API brings us back to a specific PyQt/PySide implementations which is what makes Qt.py so attractive. I guess the reason why we see so much of this in the codebase is that Maya's examples are driving people towards wrapInstance: Autodesk Docs: Working with PySide in Maya I would love for us to be able to produce a translation of that document as "Working with Qt.py in Maya". If people are happy for it to be added - I'd be happy to help put together a patch. |
Good idea. Perhaps as an extension of Fredrik's document. To include pros and cons of these binding-specific methods. Could also be elsewhere, even in the README of this project since it maybe relevant enough. |
To follow up on my post from 1st Oct, here's an example of where we use the delete function: def deleteLayout(widget, layout=None, deleteTopLevelLayout=True):
""" Function to delete a widget's layout. This needs to be done in
reverse order of each item in the layout as the index is changed
for each itemAt() call.
If deleteTopLevelLayout==True, the layout is deleted using the sip
module to ensure that all underlying C++ destructors are called,
otherwise the layout will have stale widgets from the previous
setLayout() calls
""""
if widget is not None and layout is None:
layout = widget.layout()
if layout is not None:
for i in xrange(layout.count()-1, -1, -1):
item = layout.itemAt(i)
if isinstance(item, QtGui.QWidgetItem):
item.widget().setParent(None)
elif isinstance (item, QtGui.QLayoutItem):
# recursive call if the item is a layout
deleteLayout(item)
if deleteTopLevelLayout:
import sip
sip.delete(layout) |
That is perfect @innerhippy! Thanks for that! |
In case you decide to start doing this, you can fork this project and make a pull request with the translation. If anyone has the time he/she can join in and help out by making pull requests to your fork. This way it could become a collaborative process.
If this were to be created, I think it would be useful to keep it as a markdown file (linked from the README) here in the repository. |
Hi guys, For sure a wrapInstance binding will be awesome but I let this decision up to you :) |
Hi @glm-nchaverou! Thanks for the kind words, and you are most welcome to join the discussion. I can see the use of wrapping sip/shiboken, but I'm still out the lookout for reason enough to warrant it. So far, the uses I have seen and been shown have had a non-sip equivalent; the most prominent being parenting to the main Maya window.
If you could provide me with an example of doing what you're looking to do, in this case modifying the Attribute Editor, using sip/shiboken, then I'd be happy to try and find a non-sip/shiboken equivalent. If there is none, then that would be the best of reasons to start looking at wrapping enough of it to facilitate what you are looking to do. |
I couldn't find how to get the Attribute Editor, but if it's similar to finding the Channel Box, then maybe this could be of help. 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}") Pure 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}") |
Hey, Ok here's how we hacked the AttributeEditor (can't find who we inspired this first... sorry) In the AEMyNodeTemplate.mel file:
And on the Python side: def buildQT(layout):
mayaLayoutPtr = omui.MQtUtil.findLayout(layout)
mayaLayout = wrapInstance(long(mayaLayoutPtr), QtWidgets.QWidget)
mayaLayoutObj = mayaLayout.findChild(QtWidgets.QBoxLayout)
#add the widget to the layout
mayaLayoutObj.insertWidget(0,widget) It should highlight eveything :) |
Hi @glm-nchaverou, I'm having trouble running your example with the Attribute Editor Template. Could you post a full template for me, along with how I use it? |
Hi @ben-hearn-sb, Let me know if this works for you. from maya import mel
from Qt import QtWidgets
def getMayaStatusLine():
window = {o.objectName(): o for o in QtGui.qApp.topLevelWidgets()}["MayaWindow"]
gStatusLine = mel.eval('$temp=$gStatusLineForm')
statusLine = window.findChild(QtGui.QWidget, gStatusLine)
return statusLine
statusLine = getMayaStatusLine()
statusLine.setStyleSheet("QWidget {background: red}") Having tried a few examples at this point, the pattern seems to be that wherever there is a |
Hey @mottosso, sorry for the late answer was stucked with work those past days
Let me know if you have any question |
Thanks @glm-nchaverou, can't quite get it to work. Here's what I did.
Sorry, I have no experience with AETemplates and Autodesk's help page haven't been very helpful. What am I doing wrong? |
Oops sorry, assumed you're using Maya regularly :) |
Haha, I do. Just not AETemplates.
Ok, that did the trick. Thanks. The Python file had a reference to How can I reload the AETemplate? Do I restart Maya each time? |
Ah yes sorry !! I should have warned about Qt.py :) but, well, looks like you're used to use it :) |
Ok, here we go. from Qt import QtWidgets, QtCore
# Cache main window so that it is only computed once.
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
def buildQT(layout, node):
"""Build QT Widget"""
ae = window.findChild(QtCore.QObject, "MainAttributeEditorLayout")
form = ae.findChild(QtCore.QObject, "AEcontrolFormLayout")
# The last element of the AE layout is expected to differ across runs
# or opening/closing of the AE, so we grab the last entry.
mayaLayout = form.findChild(QtCore.QObject, layout.rsplit("|", 1)[-1])
mayaLayoutObj = mayaLayout.findChild(QtWidgets.QBoxLayout)
widget = QtWidgets.QLabel()
widget.setText('my uber awesome label')
mayaLayoutObj.insertWidget(0, widget)
def updateQT(layout, node):
pass No other change to the template or plug-in. The trick here was to look at the value of MayaWindow|MainAttributeEditorLayout|formLayout2|AEmenuBarLayout|AErootLayout|AEStackLayout|AErootLayoutPane|AEbaseFormLayout|AEcontrolFormLayout|AttrEdDummyNodeFormLayout|scrollLayout2|columnLayout4|frameLayout29|columnLayout5|columnLayout6 It's the location of the Qt widget in the widget hierarchy. With this information, we spot a few key members, such as Assuming these are fixed and consistent across versions of Maya and user sessions, the only remaining thing to do was to grab the layout, which is expected to have a unique name within this narrow space. The rest is noise. Let me know what you think. |
Looks good enough to me. |
If you wanted to play it safe, you could traverse the hierarchy as given. I'd imagine this is what Maya's This runs in the Maya Script Editor, though odds are the final element will differ because of the dynamic generation of Maya's Attribute Editor. The concept however is sound. from Qt import QtWidgets, QtGui, QtCore
hierarchy = "MayaWindow|MainAttributeEditorLayout|formLayout2|AEmenuBarLayout|AErootLayout|AEStackLayout|AErootLayoutPane|AEbaseFormLayout|AEcontrolFormLayout|AttrEdDummyNodeFormLayout|scrollLayout2|columnLayout4|frameLayout29|columnLayout5|columnLayout6"
window = {o.objectName(): o for o in QtWidgets.QApplication.instance().topLevelWidgets()}["MayaWindow"]
# Previous method
ae = window.findChild(QtCore.QObject, "MainAttributeEditorLayout")
form = ae.findChild(QtCore.QObject, "AEcontrolFormLayout")
layout = form.findChild(QtCore.QObject, hierarchy.rsplit("|", 1)[-1])
# Safe method
parent = window
for child in hierarchy.split("|")[1:]:
parent = parent.findChild(QtCore.QObject, child)
# Prove that the results are identical
assert parent == layout The final child is guaranteed to exist, as it is given by Maya. As your code runs synchronously, there is no chance of the hierarchy changing whilst it runs. Having said that, I'm sure there is a better way of achieving your goal if we aren't trying to mimic code written specifically for use with shiboken/sip. |
Hi, Thus a Layout which is named.. MayaWindow|MainAttributeEditorLayout|formLayout87|AEmenuBarLayout|AErootLayout|AEStackLayout|AErootLayoutPane|AEbaseFormLayout|AEcontrolFormLayout|AttrEdCrowdChOpSensorInputFormLayout|scrollLayout2|columnLayout5|frameLayout37|columnLayout8|columnLayout9 ..in the Main AE is now named.. MayaWindow|nexFloatWindow|paneLayout1|scrollLayout1|formLayout46|formLayout48|frameLayout9|frameLayout10|formLayout49 ...in a floating one. OpenMayaUi findLayout works like a charm in this situation. Editor note: Formatted long layout names |
Not a problem! This isn't over until we've found at least 1 case where a cross-compatible solution is not possible. If you could post an example of what doesn't work, I'd be happy to take a closer look. |
Just wanted to add my 2c. I think it would be great if there was an abstraction to wrapInstance as we use it in a few tools especially when we mix Python/C++ Qt, and it's not just limited to Maya. There are actually quite a few tools that are useful in sip/shiboken unfortunately they don't keep those consistent which is annoying. But wrapInstance/wrapinstance would definitely get the most mileage. I think @boglin had the best implementation for this: https://github.com/boglin/Qt.py/tree/c_binding |
Thanks @dgovil. To everyone, please let me know if this is just me. So far, there are 46 comments in this thread. Half of them is requests for
Now I would be most delighted to include something I felt was worth including, but that worth can't be built on anything but communication. The request you are making is nonsensical and unnecessary. So tell me, what would you do? Against my better judgement and due to the overwhelming number of requests, I've made a PR just now with a cross-compatible solution that I hope will live up to your expectations. |
I agree with you on that ideally Unfortunately the other methods require some form of traversal of hierarchies or knowing what elements are called (which may change in the future) whereas if there's a guaranteed way to get a pointer from c++ or Python (for example with Maya's getMainWindow function) , the directness would be worth it. There's also the matter of education. The majority of reference and experience so far online says to use wrapInstance. While alternatives exist like you suggested, people will end up using sip/shiboken directly which kind of negates some of the benefits of qt.py in not having to worry about what binding is being used. So yes I totally agree with you on that there are better ways to do it, but I think it's just so common that it would be beneficial to have it here. I realize that potentially opens the floodgate of "well what about this feature?" But I think that the rest of sip/shiboken are so incompatible it won't happen. Your PR looks like it hits the sweet spot of giving the most bang for the buck. |
Hello all, Sorry for the late replies we have jumped back onto Maya2017 integration here at the studio and I am ready to give my 2cents back to the discussion. I am testing the proposed solutions now. They work for the statusline and the main maya window although @mottosso suggestion does not work for me at the moment. Is it absolutely necessary to replace the Shiboken stuff since I am only using it for the statusline? I am getting the main Maya window and storing it like so: def storeMayaWindow():
ptr = apiUI.MQtUtil.mainWindow()
if ptr is not None:
app = QtWidgets.QApplication.instance()
global_config.mayaMainWindow = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"] and returning my statusline like so: def getMayaStatusLine():
gStatusLine = mel.eval('$temp=$gStatusLineForm')
ptr = apiUI.MQtUtil.findControl(gStatusLine)
if ptr is not None:
statusLine = shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)
#statusLine = c_binding.wrapInstance(long(ptr), QtWidgets.QWidget)
return statusLine.children()[1].children()[1] |
Hi @ben-hearn-sb,
Could you let me know what exactly isn't working? |
@mottosso yes of course it was this snippet that did not work for me
|
Thanks Ben, here's a modified copy that works. from maya import mel
from Qt import QtWidgets
def getMayaStatusLine():
app = QtWidgets.QApplication.instance()
window = {o.objectName(): o for o in app.topLevelWidgets()}["MayaWindow"]
gStatusLine = mel.eval('$temp=$gStatusLineForm')
statusLine = window.findChild(QtWidgets.QWidget, gStatusLine)
return statusLine
statusLine = getMayaStatusLine()
statusLine.setStyleSheet('QWidget {background: "steelblue"}') Changes:
|
I'm not sure if this affects any future feature of Qt.py, such as a wrapper around shiboken, but I figured I'd just mention it:
Please see more here at the PySide mailing list: |
Hopefully this is the right issue, but I'm hearing from our developers it is occasionally required to use |
@OEP It's better suited for a separate issue, I think. Since it's a missing member. If you pop one up, or better yet, submit a PR for it, I'm happy to see this included! |
Just doing some autumn cleaning... |
Originally asked by Pierre Augeard:
The text was updated successfully, but these errors were encountered: