3D GL Widget capabilities (item selection, panning)

2,364 views
Skip to first unread message

Mathew Schwartz

unread,
Jan 13, 2014, 1:42:13 AM1/13/14
to pyqt...@googlegroups.com
Hi Luke and others,  

I have two things I am trying to do,

first, I have some scatter plot items and lines plotted in 3d, is it possible to know when i am clicking on one of the items or get a xyz location of where i am clicking?

second,  When zoomed in away from the 0,0,0 coordinate, panning is really difficult and take a long time,  has anyone worked with other methods in which to navigate. I noticed in some 3d programs i am using that the panning works equally as fast zoomed in or out and the rotations happen around the current view, not the center point.  I assume this could be done in glviewwidget but i wanted to just check if someone had the code before i dive in.

Thanks!

Luke Campagnola

unread,
Jan 13, 2014, 9:36:52 AM1/13/14
to pyqt...@googlegroups.com
On Mon, Jan 13, 2014 at 1:42 AM, Mathew Schwartz <umc...@gmail.com> wrote:
first, I have some scatter plot items and lines plotted in 3d, is it possible to know when i am clicking on one of the items or get a xyz location of where i am clicking?

Sure: call glView.itemsAt((x, y, w, h)) to get a list of all items inside the box from (x,y) to (x+w,y+h), where each argument is expressed in pixels with (0,0) at the upper-left corner of the view.. 

 
second,  When zoomed in away from the 0,0,0 coordinate, panning is really difficult and take a long time,  has anyone worked with other methods in which to navigate. I noticed in some 3d programs i am using that the panning works equally as fast zoomed in or out and the rotations happen around the current view, not the center point.  I assume this could be done in glviewwidget but i wanted to just check if someone had the code before i dive in.

I don't have this problem.. for example, run the GLBarGraphItem example. Zoom way out using the mouse wheel, then pan by middle-dragging. You should find that panning follows the mouse fairly well?  Or perhaps I have misunderstood the problem?


Luke

Mathew Schwartz

unread,
Jan 13, 2014, 9:49:04 PM1/13/14
to pyqt...@googlegroups.com
Hi Luke,

Thanks for the info I will try that out.  

For the navigation I am talking about, i have attached two videos.  One shows what happens when I zoom in and try to rotate.  Is it possible to keep the camera target with the panning, so that if i pan to the side of the coordinate system my rotation center point changes.  The other problem it shows is a problem with zooming and trying to pan.  

I also just noticed that i cant pan up and down, the other video shows that movement in a commercial program.



--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyqtgraph/CACZXET8mWCLzjp-1UN1MHo9TJraWsyh_803sSwRjA8_pkrWG-w%40mail.gmail.com.
For more options, visit https://groups.google.com/groups/opt_out.

Mathew Schwartz

unread,
Jan 14, 2014, 2:11:50 AM1/14/14
to pyqt...@googlegroups.com
Hi Luke, could you also help with mouse position?  

I have itemsAt working, however,  I cant get the mouse location in 3D.  I used this in my window class,

    # def mousePressEvent(self, event):
        # if event.button() == QtCore.Qt.RightButton:
            # self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
            # event.accept()
            # print self.dragPosition

and it works, but only for the 2d graph, nothing happens in 3d.  I noticed some mouse events in glviewwidget but I am not sure how to wait for a mouse event in the 3d view in order to invoke them

thanks


Luke Campagnola

unread,
Jan 14, 2014, 8:43:52 PM1/14/14
to pyqt...@googlegroups.com
On Mon, Jan 13, 2014 at 9:49 PM, Mathew Schwartz <umc...@gmail.com> wrote:
For the navigation I am talking about, i have attached two videos.  One shows what happens when I zoom in and try to rotate.  Is it possible to keep the camera target with the panning, so that if i pan to the side of the coordinate system my rotation center point changes.

That is already the case!
 
 The other problem it shows is a problem with zooming and trying to pan.  

At the end of your first video, the panning is slow because you have zoomed in very close to the "look-at" point for the camera. Because it thinks you want to look at something close up, it needs to make very small adjustments to pan correctly. In reality, though, you are actually looking at something far behind the look-at point. But of course, pyqtgraph does not know what you are actually looking at. 
 
I also just noticed that i cant pan up and down, the other video shows that movement in a commercial program.

The default mouse behavior is to pan only within the z=0 plane. There is definitely room here to think about other ways mouse interaction should work. For example, perhaps panning should always move the camera along the axes orthogonal to the view, rather than within the z=0 plane. Or perhaps for your application, a first-person WASD+mouse look style is more appropriate. My recommendation is to dig in to GLViewWidget.mouseMoveEvent and decide what kind of interaction you would like to see. I would be happy to help with whatever rules you come up with..

I have itemsAt working, however,  I cant get the mouse location in 3D.  

What do you mean by "mouse location in 3D" ? A single pixel on the screen corresponds to a ray in 3D, so you need more information (such as a surface intersection) to obtain a point. 
 
I used this in my window class,
    # def mousePressEvent(self, event):
        # if event.button() == QtCore.Qt.RightButton:
            # self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
            # event.accept()
            # print self.dragPosition
and it works, but only for the 2d graph, nothing happens in 3d.

I do not have any problems overriding GLViewWidget.mousePressEvent. Can you post a code sample?
 
 I noticed some mouse events in glviewwidget but I am not sure how to wait for a mouse event in the 3d view in order to invoke them

GLViewWidget does not propagate mouse events into the view at all--the mouse*Event methods are all of the mouse interaction for opengl.


Luke

Mathew Schwartz

unread,
Jan 14, 2014, 9:55:28 PM1/14/14
to pyqt...@googlegroups.com
Thanks for the explanation, I let you know what i come up with.


For the mouse interaction, I am not too sure about how the mouse overiding works. 
My main imports for pyqtgraph are: 

from PyQt4 import QtCore, QtGui
import pyqtgraph.opengl as gl
import pyqtgraph as pg

 I have a main class that looks like:

class MoAnWin(QtGui.QMainWindow, Ui_MoAnWindow):
    def __init__(self, parent=None):
        super(MoAnWin, self).__init__(parent=parent)

Then the pyqt generated file imports at the bottom, 
from pyqtgraph.opengl import GLViewWidget
from pyqtgraph import TableWidget, PlotWidget

So far this is all just from following your documentation and help on making the pyqt gui with pyqtgraph

So when I used 
    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.RightButton:

            self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()
            print self.dragPosition

Inside of my MoAnWin class at the same level as my __init__, only the 2d graph would respond to my mouse clicks.  Am i doing something obviously wrong?  It seems like I am not specifying the opengl view or the 2d view, how could I do that?  Would you be able to give me an example within the scatterplot example of how to print the right mouse click position?

Thanks !


--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.

Luke Campagnola

unread,
Jan 15, 2014, 8:56:48 PM1/15/14
to pyqt...@googlegroups.com
On Tue, Jan 14, 2014 at 9:55 PM, Mathew Schwartz <umc...@gmail.com> wrote:
Thanks for the explanation, I let you know what i come up with.

For the mouse interaction, I am not too sure about how the mouse overiding works. 
My main imports for pyqtgraph are: 

from PyQt4 import QtCore, QtGui
import pyqtgraph.opengl as gl
import pyqtgraph as pg

 I have a main class that looks like:

class MoAnWin(QtGui.QMainWindow, Ui_MoAnWindow):
    def __init__(self, parent=None):
        super(MoAnWin, self).__init__(parent=parent)

Then the pyqt generated file imports at the bottom, 
from pyqtgraph.opengl import GLViewWidget
from pyqtgraph import TableWidget, PlotWidget

So far this is all just from following your documentation and help on making the pyqt gui with pyqtgraph

It sounds like you have used Qt designer to create the UI, is that correct? If so, do not edit the python code that is generated--that code should be left as-is and imported into another module. More info here: http://pyqt.sourceforge.net/Docs/PyQt4/designer.html

 

So when I used 
    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.RightButton:

            self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()
            print self.dragPosition

Inside of my MoAnWin class at the same level as my __init__, only the 2d graph would respond to my mouse clicks.  Am i doing something obviously wrong?  It seems like I am not specifying the opengl view or the 2d view, how could I do that?  Would you be able to give me an example within the scatterplot example of how to print the right mouse click position?


Ah--if you want to handle mouse clicks on the GL view, then that is where you must override the mouse event handlers. Example:

    import pyqtgraph as pg
    import pyqtgraph.opengl

    class MyView(pg.opengl.GLViewWidget):
        def mousePressEvent(self, ev):
            print "Pressed button", ev.button(), "at", ev.pos()

            # If you do not accept the event, then no move/release
            # events will be received.
            ev.accept()  

        def mouseMoveEvent(self, ev):
            print "Mouse dragged to", ev.pos()

        def mouseReleaseEvent(self, ev):
            print "Mouse released."

    pg.mkQApp()
    view = MyView()
    view.show()

If you wanted to use MyView in Qt Designer, then simply promote a widget to MyView instead of GLViewWidget.

Am I understanding the problem correctly?


Luke
 

Mathew Schwartz

unread,
Jan 15, 2014, 9:23:17 PM1/15/14
to pyqt...@googlegroups.com
Hi Luke,

The example you gave me crashes so Im not sure what the problem is.  I dont get any error, if i run it in your examples window, it just opens and closes, and if I run it as a standalone python file it opens the window but then i get the not responding error.

I am using pyqt designer, and no I do not edit the generated code.  I have a main window, which is MoAnWindow, and when the ui is auto generated it is called Ui_MoAnWindow so thats what i inherit into my class called MoAnWin.  Inside of Ui_MoAnWindow (the main window) are a few things, including the 2d widget and 3d widget.  The 3d widget is called openglwidget.  Inside my main class I am able to do the signals and slots that interact with the rest of the program etc.

So, when I put 
        def mousePressEvent(self, ev):
            print "Pressed button", ev.button(), "at", ev.pos()

            # If you do not accept the event, then no move/release
            # events will be received.
            ev.accept()  

        def mouseMoveEvent(self, ev):
            print "Mouse dragged to", ev.pos()

        def mouseReleaseEvent(self, ev):
            print "Mouse released."

at the first level, outside my class (so it is on the same indentation level), it works, but again, only for the 2d graph, nothing from the opengl is working (i guess this is not overriding the opengl).

I attached two screenshots of where things are and what the look like


--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.
mouseinteraction1.jpg
mouseinteraction2.jpg

Luke Campagnola

unread,
Jan 15, 2014, 9:38:44 PM1/15/14
to pyqt...@googlegroups.com
On Wed, Jan 15, 2014 at 9:23 PM, Mathew Schwartz <umc...@gmail.com> wrote:
The example you gave me crashes so Im not sure what the problem is.  I dont get any error, if i run it in your examples window, it just opens and closes, and if I run it as a standalone python file it opens the window but then i get the not responding error.

Sorry, you probably need to add `pg.QtGui.QApplication.exec_()` to the end, or run with `python -i`
 
I am using pyqt designer, and no I do not edit the generated code.  I have a main window, which is MoAnWindow, and when the ui is auto generated it is called Ui_MoAnWindow so thats what i inherit into my class called MoAnWin.  Inside of Ui_MoAnWindow (the main window) are a few things, including the 2d widget and 3d widget.  The 3d widget is called openglwidget.  Inside my main class I am able to do the signals and slots that interact with the rest of the program etc.

So, when I put 
        def mousePressEvent(self, ev):
            print "Pressed button", ev.button(), "at", ev.pos()

            # If you do not accept the event, then no move/release
            # events will be received.
            ev.accept()  

        def mouseMoveEvent(self, ev):
            print "Mouse dragged to", ev.pos()

        def mouseReleaseEvent(self, ev):
            print "Mouse released."

at the first level, outside my class (so it is on the same indentation level), it works, but again, only for the 2d graph, nothing from the opengl is working (i guess this is not overriding the opengl).

Right, as I said, you must override the mouse handler events on the GLViewWidget, not on the main window. 

 

Mathew Schwartz

unread,
Jan 15, 2014, 9:56:26 PM1/15/14
to pyqt...@googlegroups.com
I am pretty confused, since comparing to your example, the only thing i "show" is the main window.  I tried putting the functions both inside my main window class and outside, both of them only return the 2d view mouse clicks.  I also know i am not supposed to put the class in the same file which is generated from qt designer, which is where glviewwidget is imported.  If i make a class say,

class openglwidget(pg.opengl.GLViewWidget):
    def mousePressEvent(self, ev):
        print "Pressed button", ev.button(), "at", ev.pos()

        # If you do not accept the event, then no move/release
        # events will be received.
        ev.accept()  

    def mouseMoveEvent(self, ev):
        print "Mouse dragged to", ev.pos()

    def mouseReleaseEvent(self, ev):
        print "Mouse released."

from your example, i would want to 
window = openglwidget()
window.show()

however, I dont want to show just that window, that window should already be inside the main window which shows everything.  So thats why i first assumed it should be inside the main window class, but that still only makes the 2d view work.   

Can you help explain this to me?

thanks


--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.

Mathew Schwartz

unread,
Jan 15, 2014, 11:30:19 PM1/15/14
to pyqt...@googlegroups.com
Hi Luke,

To make it more clear, i have attached two files which represent my situation.  Currently, only the 2d graphics returns the mouse click.  In this situation how would i fix it?

Thanks
doubleGUI.py
main.py

Luke Campagnola

unread,
Jan 16, 2014, 9:16:49 PM1/16/14
to pyqt...@googlegroups.com
On Wed, Jan 15, 2014 at 9:56 PM, Mathew Schwartz <umc...@gmail.com> wrote:
I am pretty confused, since comparing to your example, the only thing i "show" is the main window.

But your main window is *not* the GLViewWidget, so the methods you are overriding are completely ignored when the user clicks on the GL view. (and to be honest, I don't understand why the event handlers are called when you click on the 2D view)

In the example I gave, there is no QMainWindow at all; the mouse events are defined on GLViewWidget.  (you can actually call show() on any QWidget and it will appear in its own window if it does not already live inside a QMainWindow). 

You need to do these things:
1) Make your own GLViewWidget subclass, with the mouse events redefined in that class.
2) In designer, promote the view widget to your new subclass, rather than GLViewWidget.


Luke

Mathew Schwartz

unread,
Jan 16, 2014, 9:32:34 PM1/16/14
to pyqt...@googlegroups.com
When I promote the widget, i assume I stay with Qwidget as base class.  Then I promote to say, myview, and then do I also change the header file from pyqtgraph.opengl to something else?  or do I actually make the subclass inside of the glviewwidget file, and in that file inherit the glviewwidget... which i dont think is right since glviewwidget already has the mouse events so it would seem redundant to make it again.  I ask since I had tried from your previous comment to promote to a new class, but i left the header as pyqtgraph.opengl and as you suspect it gave me the error that it cannot import it because the file does not exist.

I found this http://www.qtcentre.org/threads/44804-Qt-Signals-and-Slots-with-Qt-Designer?s=f883ff0dd8d5eede557c6c2d225b1dfb  and i realize it is what you are saying, but i needed more information since I can not find out how to do this.  I read http://www.slideshare.net/FreeLeaks/rapid-gui-programming-with-python-and-qt-the-definitive-guide-to-py-qt-programming-2008  but i also could not find the information.  Sorry if I am asking too many simple questions.


--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.

Mathew Schwartz

unread,
Jan 17, 2014, 9:57:55 AM1/17/14
to pyqt...@googlegroups.com
So I think I figured it out, it seems to be working for now.  I wanted to attach the files so anyone else looking to do this can.  Maybe luke can confirm this is correct or if it will cause problems later on that I dont know about.

The 3 files that exist are:
main.py
doublegui.py
GLsubclass.py

In the main.py file:
**************************************************
import pyqtgraph as pg
import pyqtgraph.opengl
from doubleGUI import Ui_MainWindow
from PyQt4 import QtCore, QtGui
import sys


class mainwindow(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(mainwindow, self).__init__(parent=parent)

        self.setupUi(self)
        
def main():
    app = QtGui.QApplication(sys.argv)
    
    mywindow = mainwindow()
    mywindow.show()  
    sys.exit(app.exec_())
    
main()
****************************************************************

The problem I was having was, as luke said, in pyqt designer the class should be promoted to a new class, so instead of using the header as pyqtgraph.opengl with the class name GLViewWidget, the header is the file name that has the new class, in this case GLsubclass.py and the class name should be the class name you define, in this case I have gloverride.

in GLsubclass

****************************************************************
from pyqtgraph.opengl import GLViewWidget

class gloverride(GLViewWidget):
    def mousePressEvent(self, ev):
        self.mousePos = ev.pos()
        print "Pressed button HERE", ev.button(), "at", ev.pos()
****************************************************************

now, im not sure about all the class systems, but from what i was reading everyone was using __init__ and always defining super, etc... maybe this is needed later and I have not tested it enough, but just this basic one at least gets mouse signals.  Maybe Luke can elaborate on if this is correct or it does need the super etc...

then for the gui that is generated, it looks like:
****************************************************************
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'TestWindow.ui'
#
# Created: Fri Jan 17 11:13:29 2014
#      by: PyQt4 UI code generator 4.9.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(911, 768)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.openglwidget = gloverride(self.centralwidget)
        self.openglwidget.setGeometry(QtCore.QRect(389, 16, 270, 309))
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(100)
        sizePolicy.setVerticalStretch(100)
        sizePolicy.setHeightForWidth(self.openglwidget.sizePolicy().hasHeightForWidth())
        self.openglwidget.setSizePolicy(sizePolicy)
        self.openglwidget.setStyleSheet(_fromUtf8("border:none;"))
        self.openglwidget.setObjectName(_fromUtf8("openglwidget"))
        self.topgraphicsView = PlotWidget(self.centralwidget)
        self.topgraphicsView.setGeometry(QtCore.QRect(98, 138, 256, 227))
        self.topgraphicsView.setStyleSheet(_fromUtf8("border:none;"))
        self.topgraphicsView.setObjectName(_fromUtf8("topgraphicsView"))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 911, 18))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))

from GLsubclass import gloverride
from pyqtgraph import PlotWidget
****************************************************************

Luke Campagnola

unread,
Jan 18, 2014, 11:22:39 PM1/18/14
to pyqt...@googlegroups.com
On Fri, Jan 17, 2014 at 9:57 AM, Mathew Schwartz <umc...@gmail.com> wrote:
So I think I figured it out, it seems to be working for now.  I wanted to attach the files so anyone else looking to do this can.  Maybe luke can confirm this is correct or if it will cause problems later on that I dont know about.

[ snip ]

Looks good! That's what I was aiming for. 

now, im not sure about all the class systems, but from what i was reading everyone was using __init__ and always defining super, etc... maybe this is needed later and I have not tested it enough, but just this basic one at least gets mouse signals.  Maybe Luke can elaborate on if this is correct or it does need the super etc...

It is only necessary to extend __init__ if you want to change that method in some way. Otherwise, just let it inherit the method from the superclass. Likewise, you only need to call super() if you want to call the superclass' methods from your subclass methods. These are pretty general Python concepts and are not much different when dealing with PyQt.

Mathew Schwartz

unread,
Jan 19, 2014, 12:34:01 AM1/19/14
to pyqt...@googlegroups.com
Great thanks for confirming and helping me through it!


--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.

Mathew Schwartz

unread,
Jan 22, 2014, 5:03:15 AM1/22/14
to pyqt...@googlegroups.com
Incase anyone wants to do the same modification for 3d scene navigation,

I modified the pan function (i guess override? since its in my new glsubclass) so that the up and down mouse movement pans in the vertical direction, and left and right pan in horizontal.  it was making both z and y vectors 0,0,1 and using lukes normalization only on the x.  I find it much more comfortable to navigate with this method since the scroll wheel allows for zooming in and out, which is, from what i can see, almost the same functionality as moving in the z plane

    def pan(self, dx, dy, dz, relative=False):
        """
        Moves the center (look-at) position while holding the camera in place. 
        
        If relative=True, then the coordinates are interpreted such that x
        if in the global xy plane and points to the right side of the view, y is
        in the global xy plane and orthogonal to x, and z points in the global z
        direction. Distances are scaled roughly such that a value of 1.0 moves
        by one pixel on screen.
        
        """
        if not relative:
            self.opts['center'] += QtGui.QVector3D(dx, dy, dz)
        else:
            cPos = self.cameraPosition()
            cVec = self.opts['center'] - cPos
            dist = cVec.length()  ## distance from camera to center
            xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)  ## approx. width of view at distance of center point
            xScale = xDist / self.width()
            zVec = QtGui.QVector3D(0,0,1)
            xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized()
            #xVec = QtGui.QVector3D(0,1,0)
            #yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized()
            yVec = QtGui.QVector3D(0,0,1)
            self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz
        self.update()

robert luo

unread,
Jan 19, 2016, 6:59:51 AM1/19/16
to pyqtgraph
Hi Luke,

I got a problem about pyqtgraph. I really hope you can help me out.

I want to add a icon into the 3D figure which I generated using glviewwidget. Add by drag this icon with mouse, the position of it can be updated in 3D dimension, just like in a game. Could you please tell me how can I meet this demand. I really appreciate your help.

Robert
Reply all
Reply to author
Forward
0 new messages