class PyMOLGLWidget(BaseGLWidget): ''' PyMOL OpenGL Widget ''' # mouse button map _buttonMap = { Qt.LeftButton: 0, Qt.MidButton: 1, Qt.RightButton: 2, } def __init__(self, parent): self.gui = parent self.fb_scale = 1.0 # OpenGL context setup if USE_QOPENGLWIDGET: f = QtGui.QSurfaceFormat() else: f = QtOpenGL.QGLFormat() from pymol.invocation import options # logic equivalent to layer5/main.cpp:launch if options.multisample: f.setSamples(4) if options.force_stereo != -1: # See layer1/Setting.h for stereo modes if options.stereo_mode in (1, 12) or ( options.stereo_mode == 0 and AUTO_DETECT_STEREO): f.setStereo(True) if options.stereo_mode in (11, 12) and not USE_QOPENGLWIDGET: f.setAccum(True) if USE_QOPENGLWIDGET: super(PyMOLGLWidget, self).__init__(parent=parent) self.setFormat(f) else: super(PyMOLGLWidget, self).__init__(f, parent=parent) # pymol instance self.pymol = PyMOL() self.pymol.start() self.cmd = self.pymol.cmd # capture python output for feedback import pcatch pcatch._install() # for passive move drag self.setMouseTracking(True) # for accepting keyboard input (command line, shortcuts) self.setFocusPolicy(Qt.ClickFocus) # for idle rendering self._timer = QtCore.QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self._pymolProcess) # drag n drop self.setAcceptDrops(True) # pinch-zoom self.grabGesture(Qt.PinchGesture) def sizeHint(self): # default 640 + internal_gui, 480 + internal_feedback return QtCore.QSize(860, 498) ########################## # Input Events ########################## def event(self, ev): if ev.type() == Gesture: return self.gestureEvent(ev) return super(PyMOLGLWidget, self).event(ev) def gestureEvent(self, ev): gesture = ev.gesture(Qt.PinchGesture) if gesture is None: return False if gesture.state() == Qt.GestureStarted: self.pinch_start_z = self.cmd.get_view()[11] changeFlags = gesture.changeFlags() if changeFlags & QtWidgets.QPinchGesture.RotationAngleChanged: delta = gesture.lastRotationAngle() - gesture.rotationAngle() self.cmd.turn('z', delta) if changeFlags & QtWidgets.QPinchGesture.ScaleFactorChanged: view = list(self.cmd.get_view()) # best guess for https://bugreports.qt.io/browse/QTBUG-48138 totalscalefactor = gesture.totalScaleFactor() if totalscalefactor == 1.0: totalscalefactor = gesture.scaleFactor() z = self.pinch_start_z / totalscalefactor delta = z - view[11] view[11] = z view[15] -= delta view[16] -= delta self.cmd.set_view(view) return True def mouseMoveEvent(self, ev): self.pymol.drag(int(self.fb_scale * ev.x()), int(self.fb_scale * (self.height() - ev.y())), get_modifiers(ev)) def mousePressEvent(self, ev, state=0): if ev.button() not in self._buttonMap: return self.pymol.button(self._buttonMap[ev.button()], state, int(self.fb_scale * ev.x()), int(self.fb_scale * (self.height() - ev.y())), get_modifiers(ev)) def mouseReleaseEvent(self, ev): self.mousePressEvent(ev, 1) def wheelEvent(self, ev): pymolmod = get_modifiers(ev) try: delta = ev.delta() except AttributeError: # Qt5 angledelta = ev.angleDelta() delta = angledelta.y() if abs(delta) < abs(angledelta.x()): # Shift+Wheel emulates horizontal scrolling if not (ev.modifiers() & Qt.ShiftModifier): return delta = angledelta.x() if not delta: return button = 3 if delta > 0 else 4 args = (int(self.fb_scale * ev.x()), int(self.fb_scale * (self.height() - ev.y())), pymolmod) self.pymol.button(button, 0, *args) self.pymol.button(button, 1, *args) ########################## # OpenGL ########################## def paintGL(self): if not USE_QOPENGLWIDGET: glViewport(0, 0, int(self.fb_scale * self.width()), int(self.fb_scale * self.height())) self.pymol.draw() self._timer.start(0) def resizeGL(self, w, h): if USE_QOPENGLWIDGET: w = int(w * self.fb_scale) h = int(h * self.fb_scale) self.pymol.reshape(w, h, True) def updateFbScale(self, context): '''Update PyMOL's display scale factor from the window or screen context @type context: QWindow or QScreen ''' self.fb_scale = context.devicePixelRatio() try: self.cmd.set('display_scale_factor', int(self.fb_scale)) except BaseException as e: # fails with modal draw (mpng ..., modal=1) print(e) def initializeGL(self): # Scale framebuffer for Retina displays try: window = self.windowHandle() # QOpenGLWidget workaround if window is None: window = self.parent().windowHandle() self.updateFbScale(window) window.screenChanged.connect(self.updateFbScale) window.screen().physicalDotsPerInchChanged.connect( lambda dpi: self.updateFbScale(window)) except AttributeError: # Fallback for Qt4 pass def _pymolProcess(self): idle = self.pymol.idle() if idle or self.pymol.getRedisplay(): self.update() self._timer.start(20) ########################## # drag n drop ########################## def dragEnterEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() def dropEvent(self, event): if event.mimeData().hasUrls: for url in event.mimeData().urls(): if url.isLocalFile(): url = url.toLocalFile() else: url = url.toString() self.gui.load_dialog(url) event.accept()
class PyMOLGLWidget(BaseGLWidget): ''' PyMOL OpenGL Widget ''' # mouse button map _buttonMap = { Qt.LeftButton: 0, Qt.MidButton: 1, Qt.RightButton: 2, } def __init__(self, parent): self.gui = parent # OpenGL context setup if USE_QOPENGLWIDGET: f = QtGui.QSurfaceFormat() else: f = QtOpenGL.QGLFormat() from pymol.invocation import options # logic equivalent to layer5/main.cpp:launch if options.multisample: f.setSamples(4) if options.force_stereo != -1: # See layer1/Setting.h for stereo modes if options.stereo_mode in (1, 12) or ( options.stereo_mode == 0 and AUTO_DETECT_STEREO): f.setStereo(True) if options.stereo_mode in (11, 12) and not USE_QOPENGLWIDGET: f.setAccum(True) if USE_QOPENGLWIDGET: super(PyMOLGLWidget, self).__init__(parent=parent) self.setFormat(f) else: super(PyMOLGLWidget, self).__init__(f, parent=parent) # pymol instance self.pymol = PyMOL() self.pymol.start() self.cmd = self.pymol.cmd # capture python output for feedback import pcatch pcatch._install() # for passive move drag self.setMouseTracking(True) # for accepting keyboard input (command line, shortcuts) self.setFocusPolicy(Qt.ClickFocus) # for idle rendering self._timer = QtCore.QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self._pymolProcess) # drag n drop self.setAcceptDrops(True) # pinch-zoom self.grabGesture(Qt.PinchGesture) def sizeHint(self): # default 640 + internal_gui, 480 + internal_feedback return QtCore.QSize(860, 498) ########################## # Input Events ########################## def event(self, ev): if ev.type() == Gesture: return self.gestureEvent(ev) return super(PyMOLGLWidget, self).event(ev) def gestureEvent(self, ev): gesture = ev.gesture(Qt.PinchGesture) if gesture is None: return False if gesture.state() == Qt.GestureStarted: self.pinch_start_z = self.cmd.get_view()[11] changeFlags = gesture.changeFlags() if changeFlags & QtWidgets.QPinchGesture.RotationAngleChanged: delta = gesture.lastRotationAngle() - gesture.rotationAngle() self.cmd.turn('z', delta) if changeFlags & QtWidgets.QPinchGesture.ScaleFactorChanged: view = list(self.cmd.get_view()) # best guess for https://bugreports.qt.io/browse/QTBUG-48138 totalscalefactor = gesture.totalScaleFactor() if totalscalefactor == 1.0: totalscalefactor = gesture.scaleFactor() z = self.pinch_start_z / totalscalefactor delta = z - view[11] view[11] = z view[15] -= delta view[16] -= delta self.cmd.set_view(view) return True def mouseMoveEvent(self, ev): self.pymol.drag(int(self.fb_scale * ev.x()), int(self.fb_scale * (self.height() - ev.y())), get_modifiers(ev)) def mousePressEvent(self, ev, state=0): if ev.button() not in self._buttonMap: return self.pymol.button(self._buttonMap[ev.button()], state, int(self.fb_scale * ev.x()), int(self.fb_scale * (self.height() - ev.y())), get_modifiers(ev)) def mouseReleaseEvent(self, ev): self.mousePressEvent(ev, 1) def wheelEvent(self, ev): pymolmod = get_modifiers(ev) try: delta = ev.delta() except AttributeError: # Qt5 angledelta = ev.angleDelta() delta = angledelta.y() if abs(delta) < abs(angledelta.x()): # Shift+Wheel emulates horizontal scrolling if not (ev.modifiers() & Qt.ShiftModifier): return delta = angledelta.x() if not delta: return button = 3 if delta > 0 else 4 args = (int(self.fb_scale * ev.x()), int(self.fb_scale * (self.height() - ev.y())), pymolmod) self.pymol.button(button, 0, *args) self.pymol.button(button, 1, *args) ########################## # OpenGL ########################## def paintGL(self): if not USE_QOPENGLWIDGET: glViewport(0, 0, int(self.fb_scale * self.width()), int(self.fb_scale * self.height())) self.pymol.draw() self._timer.start(0) def resizeGL(self, w, h): if USE_QOPENGLWIDGET: w = int(w * self.fb_scale) h = int(h * self.fb_scale) self.pymol.reshape(w, h, True) def updateFbScale(self, context): '''Update PyMOL's display scale factor from the window or screen context @type context: QWindow or QScreen ''' self.fb_scale = context.devicePixelRatio() try: self.cmd.set('display_scale_factor', int(self.fb_scale)) except BaseException as e: # fails with modal draw (mpng ..., modal=1) print(e) def initializeGL(self): # Scale framebuffer for Retina displays try: window = self.windowHandle() # QOpenGLWidget workaround if window is None: window = self.parent().windowHandle() self.updateFbScale(window) window.screenChanged.connect(self.updateFbScale) except AttributeError: # Fallback for Qt4 self.fb_scale = 1.0 def _pymolProcess(self): idle = self.pymol.idle() if idle or self.pymol.getRedisplay(): self.update() self._timer.start(20) ########################## # drag n drop ########################## def dragEnterEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() def dropEvent(self, event): if event.mimeData().hasUrls: for url in event.mimeData().urls(): if url.isLocalFile(): url = url.toLocalFile() else: url = url.toString() self.gui.load_dialog(url) event.accept()
3ALA C 23 3.013 2.747 1.191 3ALA O 24 3.066 2.635 1.188 3ALA CB 25 2.813 2.842 1.316 3ALA H 26 2.848 2.549 1.189 3ALA HA 27 2.835 2.820 1.100 3ALA 1HB 28 2.703 2.857 1.313 3ALA 2HB 29 2.836 2.789 1.410 3ALA 3HB 30 2.858 2.942 1.322 3.41363 3.41363 2.41380 0.00000 0.00000 0.00000 0.00000 1.70681 1.70681 """ with open(GRO_PATH, "wt") as f: f.write(grodata) pymol = SingletonPyMOL() pymol.start() cmd = pymol.cmd cmd.load(CIF_PATH) # Test rendering cmd.png(PNG_PATH, ray=1) assert isfile(PNG_PATH) # Test correct integration of NumPy assert cmd.get_coordset(PDB_ID).shape == (304, 3) # Test if we can load a .gro file cmd.load(GRO_PATH, 'grodata') assert cmd.get_coordset('grodata').shape == (30, 3)