def wait_for_true(self, fun, timeout_sec=1.0): t = QTime() t.start() timeout_millisec = int(timeout_sec * 1000) while not fun(): if t.elapsed() > timeout_millisec: raise StopIteration() yield
def get_child(widget, object_name, child_type=QWidget, timeout=3): t = QTime() t.start() timeout *= 1000 children = [] while len(children) == 0 and t.elapsed() < timeout: children = widget.findChildren(child_type, object_name) # QApplication.processEvents() if len(children) == 0: raise RuntimeError("Widget doesn't have child with name {}".format(object_name)) if len(children) > 1: print('Widget has more than 1 child with name {}'.format(object_name)) return children[0]
def get_child(widget, object_name, child_type=QWidget, timeout=3): t = QTime() t.start() timeout *= 1000 children = [] while len(children) == 0 and t.elapsed() < timeout: children = widget.findChildren(child_type, object_name) # QApplication.processEvents() if len(children) == 0: raise RuntimeError( "Widget doesn't have child with name {}".format(object_name)) if len(children) > 1: print('Widget has more than 1 child with name {}'.format(object_name)) return children[0]
class HalPlot(QWidget, HALWidget): """HAL Plot Plot for displaying HAL pin values. Input pin type is HAL type ( float). Up to four HAL pin values can be plotted .. table:: Generated HAL Pins ========================= =========== ========= HAL Pin Name Type Direction ========================= =========== ========= qtpyvcp.seriesXname.in float in ========================= =========== ========= """ def __init__(self, parent=None): super(HalPlot, self).__init__(parent) # HAL sampling frequency parameters self._frequency = 1 # Hz self._timeWindow = 600 # seconds # Internal timestamp for x-axis - data values are ms from when "timestamp" was started self.timestamp = QTime() self.timestamp.start() self._legend = False self._yAxisLabel = 'y label' self._yAxisUnits = 'y units' self._minY = 0 self._maxY = 1 self._s1enable = True self._s1name = "Series 1" self._s1colour = QColor('red') self._s1width = 1 self._s1style = Qt.SolidLine self._s1_pin = None self._s2enable = False self._s2name = "Series 2" self._s2colour = QColor('blue') self._s2width = 1 self._s2style = Qt.SolidLine self._s2_pin = None self._s3enable = False self._s3name = "Series 3" self._s3colour = QColor('green') self._s3width = 1 self._s3style = Qt.SolidLine self._s3_pin = None self._s4enable = False self._s4name = "Series 4" self._s4colour = QColor('yellow') self._s4width = 1 self._s4style = Qt.SolidLine self._s4_pin = None # PyQtGraph stuff self.graph = pg.GraphicsLayoutWidget() self.yAxis = pg.AxisItem(orientation='left') self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits) self.yAxis.setGrid(125) self.plot = self.graph.addPlot(axisItems={'bottom': TimeAxisItem(orientation='bottom'), 'left': self.yAxis}) self.plot.setYRange(self._minY, self._maxY, padding=0.0) self.legend = self.plot.addLegend() self.p1 = pg.PlotCurveItem(name=self._s1name) self.p2 = pg.PlotCurveItem(name=self._s2name) self.p3 = pg.PlotCurveItem(name=self._s3name) self.p4 = pg.PlotCurveItem(name=self._s4name) self.setSeries() self.setData() self.Vlayout = QVBoxLayout(self) self.Vlayout.addWidget(self.graph) # HAL stuff self._typ = "float" self._fmt = "%s" if IN_DESIGNER: return # QTimer self.updatetimer = QTimer(self) self.updatetimer.timeout.connect(self.updateplot) self.updatetimer.start(self._refreshRate) def setSeries(self): # first remove the legend as it does not update correnctly try: self.legend.scene().removeItem(self.legend) except: pass # remove all plot items self.plot.clear() # add the legend and plot itmes if self._legend: self.legend = self.plot.addLegend() if self._s1enable: self.p1 = pg.PlotCurveItem(name=self._s1name) self.plot.addItem(self.p1) self.p1.setPen(QColor(self._s1colour), width=self._s1width, style=self._s1style) if self._s2enable: self.p2 = pg.PlotCurveItem(name=self._s2name) self.plot.addItem(self.p2) self.p2.setPen(QColor(self._s2colour), width=self._s2width, style=self._s2style) if self._s3enable: self.p3 = pg.PlotCurveItem(name=self._s3name) self.plot.addItem(self.p3) self.p3.setPen(QColor(self._s3colour), width=self._s3width, style=self._s3style) if self._s4enable: self.p4 = pg.PlotCurveItem(name=self._s4name) self.plot.addItem(self.p4) self.p4.setPen(QColor(self._s4colour), width=self._s4width, style=self._s4style) def setData(self): # Data stuff self._period = 1.0/self._frequency self._refreshRate = int(self._period * 1000) # sample period in milliseconds self._timeWindowMS = self._timeWindow * 1000 # time window in milliseconds self._bufsize = int(self._timeWindowMS / self._refreshRate) # Data containers: collections.deque is list-like container with fast appends and pops on either end self.x = np.linspace(-self.timeWindow, 0.0, self._bufsize) self.now = self.timestamp.elapsed() self.x_data = deque(np.linspace(self.now-self._timeWindowMS, self.now, self._bufsize),self._bufsize) self.s1 = np.zeros(self._bufsize, dtype=np.float) self.s1_data = deque([0.0] * self._bufsize, self._bufsize) self.s2 = np.zeros(self._bufsize, dtype=np.float) self.s2_data = deque([0.0] * self._bufsize, self._bufsize) self.s3 = np.zeros(self._bufsize, dtype=np.float) self.s3_data = deque([0.0] * self._bufsize, self._bufsize) self.s4 = np.zeros(self._bufsize, dtype=np.float) self.s4_data = deque([0.0] * self._bufsize, self._bufsize) def updateplot(self): self.x_data.append(self.timestamp.elapsed()) self.x[:] = self.x_data if self._s1enable: self.s1_data.append(self._s1_pin.value) self.s1[:] = self.s1_data self.p1.setData(self.x, self.s1) if self._s2enable: self.s2_data.append(self._s2_pin.value) self.s2[:] = self.s2_data self.p2.setData(self.x, self.s2) if self._s3enable: self.s3_data.append(self._s3_pin.value) self.s3[:] = self.s3_data self.p3.setData(self.x, self.s3) if self._s4enable: self.s4_data.append(self._s4_pin.value) self.s4[:] = self.s4_data self.p4.setData(self.x, self.s4) def setyAxis(self): self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits) def setYRange(self): self.plot.setYRange(self._minY, self._maxY, padding = 0.0) @Property(int) def frequency(self): return self._frequency @frequency.setter def frequency(self, frequency): self._frequency = frequency return self.setData() @Property(int) def timeWindow(self): return self._timeWindow @timeWindow.setter def timeWindow(self, timeWindow): self._timeWindow = timeWindow return self.setData() @Property(str) def yAxisLabel(self): return self._yAxisLabel @yAxisLabel.setter def yAxisLabel(self, yAxisLabel): self._yAxisLabel = yAxisLabel return self.setyAxis() @Property(str) def yAxisUnits(self): return self._yAxisUnits @yAxisUnits.setter def yAxisUnits(self, yAxisUnits): self._yAxisUnits = yAxisUnits return self.setyAxis() @Property(float) def minYRange(self): return self._minY @minYRange.setter def minYRange(self, minY): self._minY = minY return self.setYRange() @Property(float) def maxYRange(self): return self._maxY @maxYRange.setter def maxYRange(self, maxY): self._maxY = maxY return self.setYRange() # Legend propterties @Property(bool) def legendenable(self): return self._legend @legendenable.setter def legendenable(self, legendenable): self._legend = legendenable self.setSeries() # Series 1 properties @Property(bool) def series1enable(self): return self._s1enable @series1enable.setter def series1enable(self, series1enable): self._s1enable = series1enable self.setSeries() @Property(str) def series1name(self): return self._s1name @series1name.setter def series1name(self, series1name): self._s1name = series1name self.setSeries() @Property(QColor) def series1colour(self): return self._s1colour @series1colour.setter def series1colour(self, series1colour): self._s1colour = series1colour self.setSeries() @Property(int) def series1width(self): return self._s1width @series1width.setter def series1width(self, series1width): self._s1width = series1width self.setSeries() @Property(Qt.PenStyle) def series1style(self): return self._s1style @series1style.setter def series1style(self, series1style): self._s1style = series1style self.setSeries() # Series 2 properties @Property(bool) def series2enable(self): return self._s2enable @series2enable.setter def series2enable(self, series2enable): self._s2enable = series2enable self.setSeries() @Property(str) def series2name(self): return self._s2name @series2name.setter def series2name(self, series2name): self._s2name = series2name self.setSeries() @Property(QColor) def series2colour(self): return self._s2colour @series2colour.setter def series2colour(self, series2colour): self._s2colour = series2colour self.setSeries() @Property(int) def series2width(self): return self._s2width @series2width.setter def series2width(self, series2width): self._s2width = series2width self.setSeries() @Property(Qt.PenStyle) def series2style(self): return self._s2style @series2style.setter def series2style(self, series2style): self._s2style = series2style self.setSeries() # Series 3 properties @Property(bool) def series3enable(self): return self._s3enable @series3enable.setter def series3enable(self, series3enable): self._s3enable = series3enable self.setSeries() @Property(str) def series3name(self): return self._s3name @series3name.setter def series3name(self, series3name): self._s3name = series3name self.setSeries() @Property(QColor) def series3colour(self): return self._s3colour @series3colour.setter def series3colour(self, series3colour): self._s3colour = series3colour self.setSeries() @Property(int) def series3width(self): return self._s3width @series3width.setter def series3width(self, series3width): self._s3width = series3width self.setSeries() @Property(Qt.PenStyle) def series3style(self): return self._s3style @series3style.setter def series3style(self, series3style): self._s3style = series3style self.setSeries() # Series 4 properties @Property(bool) def series4enable(self): return self._s4enable @series4enable.setter def series4enable(self, series4enable): self._s4enable = series4enable self.setSeries() @Property(str) def series4name(self): return self._s4name @series4name.setter def series4name(self, series4name): self._s4name = series4name self.setSeries() @Property(QColor) def series4colour(self): return self._s4colour @series4colour.setter def series4colour(self, series4colour): self._s4colour = series4colour self.setSeries() @Property(int) def series4width(self): return self._s4width @series4width.setter def series4width(self, series4width): self._s4width = series4width self.setSeries() @Property(Qt.PenStyle) def series4style(self): return self._s4style @series4style.setter def series4style(self, series4style): self._s4style = series4style self.setSeries() def initialize(self): comp = hal.getComponent() obj_name = self.getPinBaseName() # add HAL pins if self._s1enable: self._s1_pin = comp.addPin(obj_name + "." + self._s1name.replace(' ', ''), self._typ, "in") if self._s2enable: self._s2_pin = comp.addPin(obj_name + "." + self._s2name.replace(' ', ''), self._typ, "in") if self._s3enable: self._s3_pin = comp.addPin(obj_name + "." + self._s3name.replace(' ', ''), self._typ, "in") if self._s4enable: self._s4_pin = comp.addPin(obj_name + "." + self._s4name.replace(' ', ''), self._typ, "in")
class ChartView3d(QGLWidget): """ Widget to display openGL items. """ def __init__(self, parent=None): """ :param parent: """ super(ChartView3d, self).__init__(parent) self.setFocusPolicy(Qt.ClickFocus) self.format().setSamples(4) self.props = ViewProperties() self.items = [] self.makeCurrent() self.cam_ctrl = CamControl(self.props, self) self.cam_ctrl.show() self.props.changed.connect(self.camera_update) self.frame_count = 0 self.frame_time = QTime() self.frame_time.start() def addItem(self, item): self.items.append(item) if hasattr(item, "initializeGL"): self.makeCurrent() item.initializeGL() item.view = self self.update() def removeItem(self, item): self.items.remove(item) item.view = None self.update() def initializeGL(self): self.resizeGL(self.width(), self.height()) glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LEQUAL) # lighting light_position = [1.0, 1.0, 2.0, 0.0] glLight(GL_LIGHT0, GL_POSITION, light_position) glMaterialfv(GL_FRONT, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0]) glMaterialf(GL_FRONT, GL_SHININESS, 100.0) def getViewport(self): vp = self.props.viewport if vp is None: return 0, 0, self.width(), self.height() else: return vp def resizeGL(self, w, h): glViewport(*self.getViewport()) self.setProjection() self.setModelview() def setProjection(self, region=None): m = self.projectionMatrix(region) glMatrixMode(GL_PROJECTION) glLoadIdentity() a = np.array(m.copyDataTo()).reshape((4, 4)) glMultMatrixf(a.transpose()) def projectionMatrix(self, region=None): # Xw = (Xnd + 1) * width/2 + X if region is None: region = (0, 0, self.width(), self.height()) x0, y0, w, h = self.getViewport() dist = self.props.distance fov = self.props.fov nearClip = dist * 0.001 farClip = dist * 1000.0 r = nearClip * np.tan(fov * 0.5 * np.pi / 180.0) t = r * h / w # convert screen coordinates (region) to normalized device coordinates # Xnd = (Xw - X0) * 2/width - 1 ## Note that X0 and width in these equations must be the values used in viewport left = r * ((region[0] - x0) * (2.0 / w) - 1) right = r * ((region[0] + region[2] - x0) * (2.0 / w) - 1) bottom = t * ((region[1] - y0) * (2.0 / h) - 1) top = t * ((region[1] + region[3] - y0) * (2.0 / h) - 1) tr = QMatrix4x4() tr.frustum(left, right, bottom, top, nearClip, farClip) return tr def setModelview(self): glMatrixMode(GL_MODELVIEW) glLoadIdentity() m = self.viewMatrix() a = np.array(m.copyDataTo()).reshape((4, 4)) glMultMatrixf(a.transpose()) def viewMatrix(self): tr = QMatrix4x4() ntr = np.eye(4, dtype=np.float) tr.translate(0.0, 0.0, -self.props.distance) ntr[0:3, 3] = [0.0, 0.0, -self.props.distance] tr.rotate(self.props.elevation_angle - 90, 1, 0, 0) tr.rotate(self.props.azimuth_angle - 90, 0, 0, -1) center = self.props.center tr.translate(-center[0], -center[1], -center[2]) return tr # def itemsAt(self, region=None): # """ # Return a list of the items displayed in the region (x, y, w, h) # relative to the widget. # """ # # #TODO: Check if it works # region = ( # region[0], # self.height() - (region[1] + region[3]), # region[2], # region[3], # ) # # # buf = np.zeros(100000, dtype=np.uint) # # buf = glSelectBuffer(100000) # try: # glRenderMode(GL_SELECT) # glInitNames() # glPushName(0) # self._itemNames = {} # self.paintGL(region=region, useItemNames=True) # # finally: # hits = glRenderMode(GL_RENDER) # # items = [(h.near, h.names[0]) for h in hits] # items.sort(key=lambda i: i[0]) # return [self._itemNames[i[1]] for i in items] def paintGL(self, region=None, viewport=None, useItemNames=False): """ viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport'] region specifies the sub-region of self.opts['viewport'] that should be rendered. Note that we may use viewport != self.opts['viewport'] when exporting. """ if viewport is None: glViewport(*self.getViewport()) else: glViewport(*viewport) bgcolor = self.props.background_color glClearColor(*bgcolor) glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT) self.drawItemTree(useItemNames=useItemNames) self.frame_count += 1 fps = self.frame_count / (self.frame_time.elapsed() / 1000.0) if CONFIG.debug: _log.debug("FPS: {}".format(fps)) def drawItemTree(self, item=None, useItemNames=False): """ :param item: :param useItemNames: :return: """ if item is None: items = [x for x in self.items if x.parentItem() is None] else: items = item.childItems() items.append(item) for i in items: if not i.visible(): continue if i is item: try: glPushAttrib(GL_ALL_ATTRIB_BITS) if useItemNames: glLoadName(i._id) self._itemNames[i._id] = i i.paint() except GLError as ex: _log.info("Error while drawing items: {}".format(ex)) if CONFIG.debug: raise finally: glPopAttrib() else: glMatrixMode(GL_MODELVIEW) glPushMatrix() try: tr = i.transform() a = np.array(tr.copyDataTo()).reshape((4, 4)) glMultMatrixf(a.transpose()) self.drawItemTree(i, useItemNames=useItemNames) finally: glMatrixMode(GL_MODELVIEW) glPopMatrix() def setCameraPosition(self, distance=None, elevation=None, azimuth=None): """ Sets the camera parameters. :param distance: distance to (0,0,0) :param elevation: elevation angle :param azimuth: azimuth angle """ if distance is not None: self.props.distance = distance if elevation is not None: self.props.elevation_angle = elevation if azimuth is not None: self.props.azimuth_angle = azimuth self.camera_update() def cameraPosition(self): """Return current position of camera based on center, dist, elevation, and azimuth""" center = self.props.center dist = self.props.distance elev = self.props.elevation_angle * np.pi / 180.0 azim = self.props.azimuth_angle * np.pi / 180.0 pos = Vector3d( center.x + dist * np.cos(elev) * np.cos(azim), center.y + dist * np.cos(elev) * np.sin(azim), center.z + dist * np.sin(elev), ) return pos def orbit(self, azim, elev): """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" self.props.azimuth_angle += azim self.props.elevation_angle = np.clip(self.props.elevation_angle + elev, -90, 90) # self.update() self.camera_update() 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.props.center += QVector3D(dx, dy, dz) else: cPos = self.cameraPosition() cVec = self.props.center - cPos dist = np.linalg.norm(cVec) xDist = ( dist * 2.0 * np.tan(0.5 * self.props.fov * np.pi / 180.0) ) ## approx. width of view at distance of center point xScale = xDist / self.width() # zVec = QVector3D(0, 0, 1) # xVec = QVector3D.crossProduct(zVec, cVec).normalized() # yVec = QVector3D.crossProduct(xVec, zVec).normalized() zVec = Vector3d(0, 0, 1) xVec = np.cross(zVec, cVec) xVec /= np.linalg.norm(xVec) yVec = np.cross(xVec, zVec) yVec /= np.linalg.norm(yVec) self.props.center = ( self.props.center + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz ) self.camera_update() def pixelSize(self, pos): """ Return the approximate size of a screen pixel at the location pos Pos may be a Vector or an (N,3) array of locations """ cam = self.cameraPosition() if isinstance(pos, np.ndarray): cam = np.array(cam).reshape((1,) * (pos.ndim - 1) + (3,)) dist = ((pos - cam) ** 2).sum(axis=-1) ** 0.5 else: dist = (pos - cam).length() xDist = dist * 2.0 * np.tan(0.5 * self.props.fov * np.pi / 180.0) return xDist / self.width() def mousePressEvent(self, ev): self._mousePos = ev.pos() def mouseMoveEvent(self, ev): """ Mouse move event handler :param ev: """ diff = ev.pos() - self._mousePos self._mousePos = ev.pos() if ev.buttons() == Qt.LeftButton: self.orbit(-diff.x(), diff.y()) # print self.opts['azimuth'], self.opts['elevation'] elif ev.buttons() == Qt.MidButton: if ev.modifiers() & Qt.ControlModifier: self.pan(diff.x(), 0, diff.y(), relative=True) else: self.pan(-diff.x(), -diff.y(), 0, relative=True) def wheelEvent(self, ev): """ :param ev: :return: """ delta = ev.angleDelta() if ev.modifiers() & Qt.ControlModifier: self.props.fov *= 0.999 ** (delta.y() / 1.0) else: self.props.distance *= 0.999 ** (delta.y() / 1.0) self.camera_update() def camera_update(self): """ Applies the updated camera view position and angles. """ self.setProjection() self.setModelview() self.update() def keyReleaseEvent(self, ev): """ Overrides key release to plug-in default view shortcuts :param ev: Key press event """ if ev.key() == Qt.Key_1: self.setCameraPosition(elevation=90, azimuth=-90) elif ev.key() == Qt.Key_2: self.setCameraPosition(elevation=0, azimuth=-90) elif ev.key() == Qt.Key_3: self.setCameraPosition(elevation=0, azimuth=0) elif ev.key() == Qt.Key_4: self.setCameraPosition(elevation=21, azimuth=-161) elif ev.key() == Qt.Key_C: if self.cam_ctrl.isVisible(): self.cam_ctrl.close() else: self.cam_ctrl.show()