pythonOCC例子搬運:5.顯示畫面上疊加資訊與動畫
阿新 • • 發佈:2019-01-06
這裡返回總目錄>>返回總目錄
core_visualization_overpaint_viewer.py
本例從https://github.com/tpaviot/pythonocc-demos搬運而來
執行版本:0.18.2
在其餘版本執行不保證正確
先上結果圖
程式碼部分
from __future__ import print_function import random import sys from OCC.Display.backend import load_any_qt_backend, get_qt_modules load_any_qt_backend() QtCore, QtGui, QtWidgets, QtOpenGL = get_qt_modules() from OCC.Display.qtDisplay import qtViewer3d # -------------------------------------------------------------------------- # these are names of actions that invoke the OpenGL viewport to be redrawn # such actions need to be invoked through the GLWidget.update method, which # in turn invoked the GLWidget.paintEvent method # this way, all command that redraw the viewport are invoked synchronously # -------------------------------------------------------------------------- ON_ZOOM = "on_zoom" ON_ZOOM_AREA = "on_zoom_area" ON_ZOOM_FACTOR = "on_zoom_factor" ON_ZOOM_FITALL = "on_zoom_fitall" ON_DYN_ZOOM = "on_dyn_zoom" ON_DYN_ROT = "on_dyn_rot" ON_DYN_PAN = "on_dyn_pan" ON_MOVE_TO = "on_move_to" ON_SHIFT_SELECT = "on_shift_select" ON_SELECT = "on_select" ON_SELECT_AREA = "on_select_area" class Bubble(object): def __init__(self, position, radius, velocity): self.position = position self.vel = velocity self.radius = radius self.innerColor = self.randomColor() self.outerColor = self.randomColor() self.updateBrush() def updateBrush(self): gradient = QtGui.QRadialGradient( QtCore.QPointF(self.radius, self.radius), self.radius, QtCore.QPointF(self.radius * 0.5, self.radius * 0.5)) gradient.setColorAt(0, QtGui.QColor(255, 255, 255, 0)) gradient.setColorAt(0.25, self.innerColor) gradient.setColorAt(1, self.outerColor) self.brush = QtGui.QBrush(gradient) def drawBubble(self, painter, mouse_intersects): painter.save() painter.translate(self.position.x() - self.radius, self.position.y() - self.radius) painter.setBrush(self.brush) if mouse_intersects: font = painter.font() font.setPointSize(20) painter.setFont(font) painter.setPen(QtCore.Qt.red) painter.drawText(0, 0, "so hovering over!!!") painter.drawEllipse(0, 0, int(2 * self.radius), int(2 * self.radius)) painter.restore() def randomColor(self): red = random.randrange(205, 256) green = random.randrange(205, 256) blue = random.randrange(205, 256) alpha = random.randrange(91, 192) return QtGui.QColor(red, green, blue, alpha) def move(self, bbox): self.position += self.vel leftOverflow = self.position.x() - self.radius - bbox.left() rightOverflow = self.position.x() + self.radius - bbox.right() topOverflow = self.position.y() - self.radius - bbox.top() bottomOverflow = self.position.y() + self.radius - bbox.bottom() if leftOverflow < 0.0: self.position.setX(self.position.x() - 2 * leftOverflow) self.vel.setX(-self.vel.x()) elif rightOverflow > 0.0: self.position.setX(self.position.x() - 2 * rightOverflow) self.vel.setX(-self.vel.x()) if topOverflow < 0.0: self.position.setY(self.position.y() - 2 * topOverflow) self.vel.setY(-self.vel.y()) elif bottomOverflow > 0.0: self.position.setY(self.position.y() - 2 * bottomOverflow) self.vel.setY(-self.vel.y()) def rect(self): return QtCore.QRectF(self.position.x() - self.radius, self.position.y() - self.radius, 2 * self.radius, 2 * self.radius) class GLWidget(qtViewer3d): def __init__(self, parent=None): super(GLWidget, self).__init__(parent) #: state self._initialized = False # no effect? self.doubleBuffer() # --------------------------------------------------------------------- # parameters for bubbles # --------------------------------------------------------------------- midnight = QtCore.QTime(0, 0, 0) random.seed(midnight.secsTo(QtCore.QTime.currentTime())) self.bubbles = [] # parameter for overpainted text box self.text = """ Example overpainting the OpenGL viewport, inspired by a similar example from Qt """ # --------------------------------------------------------------------- # create properties for the last coordinate, such that the stupid # "point" conversion takes place implicitly # --------------------------------------------------------------------- self.lastPos = QtCore.QPoint() # --------------------------------------------------------------------- # GUI parameters # --------------------------------------------------------------------- self.setMinimumSize(200, 200) self.setWindowTitle("Overpainting a Scene") # parameters for overpainting self.setAttribute(QtCore.Qt.WA_NoSystemBackground) self.setAutoFillBackground(False) # --------------------------------------------------------------------- # setup # --------------------------------------------------------------------- # start the background thread that performs the overpainting of # the OpenGL view with bubbles self._setupAnimation() # --------------------------------------------------------------------- # GUI State # --------------------------------------------------------------------- # QPoint stored on mouse press self._point_on_mouse_press = QtCore.QPoint() # QPoint stored on mouse move self._point_on_mouse_move = QtCore.QPoint() # stores the delta between self._point_on_mouse_press and self._point_on_mouse_move self._delta_event_pos = None self._current_action = self.current_action = None # mouse button state self._is_right_mouse_button_surpressed = False self._is_left_mouse_button_surpressed = False @property def point_on_mouse_press(self): x = self._point_on_mouse_press.x() y = self._point_on_mouse_press.y() return x, y @point_on_mouse_press.setter def point_on_mouse_press(self, event): if isinstance(event, QtGui.QMouseEvent): self._point_on_mouse_press = QtCore.QPoint(event.pos()) elif isinstance(event, QtCore.QPoint): self._point_on_mouse_press = QtCore.QPoint(event) @property def point_on_mouse_move(self): x = self._point_on_mouse_move.x() y = self._point_on_mouse_move.y() return x, y @point_on_mouse_move.setter def point_on_mouse_move(self, event): if isinstance(event, (QtGui.QMouseEvent, QtGui.QWheelEvent)): self._point_on_mouse_move = QtCore.QPoint(event.pos()) elif isinstance(event, QtCore.QPoint): self._point_on_mouse_move = QtCore.QPoint(event) @property def delta_mouse_event_pos(self): """delta between previous_event and next_event""" pos_a = self._point_on_mouse_press pos_b = self._point_on_mouse_move dX = pos_a.x() - pos_b.x() dY = pos_a.y() - pos_b.y() return dX, dY @property def is_right_mouse_button_surpressed(self): return self._is_right_mouse_button_surpressed @is_right_mouse_button_surpressed.setter def is_right_mouse_button_surpressed(self, value): self._is_right_mouse_button_surpressed = value @property def is_left_mouse_button_surpressed(self): return self._is_left_mouse_button_surpressed @is_left_mouse_button_surpressed.setter def is_left_mouse_button_surpressed(self, value): self._is_left_mouse_button_surpressed = value def _setupAnimation(self): self.animationTimer = QtCore.QTimer() self.animationTimer.setSingleShot(False) self.animationTimer.timeout.connect(self.animate) self.animationTimer.start(25) def mousePressEvent(self, event): self.point_on_mouse_press = event self.setFocus() button = event.button() modifiers = event.modifiers() if button == QtCore.Qt.RightButton: self.is_right_mouse_button_surpressed = True elif button == QtCore.Qt.LeftButton: self.is_left_mouse_button_surpressed = True if button == QtCore.Qt.RightButton and modifiers == QtCore.Qt.ShiftModifier: # ON_ZOOM_AREA self._drawbox = True elif button == QtCore.Qt.LeftButton and modifiers == QtCore.Qt.ShiftModifier: # ON_SELECT_AREA self._drawbox = True self._select_area = True self.is_right_mouse_button_surpressed = True def mouseReleaseEvent(self, event): button = event.button() modifiers = event.modifiers() if button == QtCore.Qt.RightButton: self.is_right_mouse_button_surpressed = False if modifiers == QtCore.Qt.ShiftModifier: self.current_action = ON_ZOOM_AREA if button == QtCore.Qt.LeftButton: self.is_left_mouse_button_surpressed = False if self._select_area: self.current_action = ON_SELECT_AREA elif modifiers == QtCore.Qt.ShiftModifier: self.current_action = ON_SHIFT_SELECT else: self.current_action = ON_SELECT self._drawbox = False self._select_area = False self.point_on_mouse_move = event self.update() def mouseMoveEvent(self, event): self.point_on_mouse_move = event buttons = int(event.buttons()) modifiers = event.modifiers() # rotate if buttons == QtCore.Qt.LeftButton and not modifiers == QtCore.Qt.ShiftModifier: self.current_action = ON_DYN_ROT # dynamic zoom elif buttons == QtCore.Qt.RightButton and not modifiers == QtCore.Qt.ShiftModifier: self.current_action = ON_DYN_ZOOM # dynamic panning elif buttons == QtCore.Qt.MidButton: self.current_action = ON_DYN_PAN # zoom window, overpaints rectangle elif buttons == QtCore.Qt.RightButton and modifiers == QtCore.Qt.ShiftModifier: self.current_action = ON_ZOOM_AREA # select area elif buttons == QtCore.Qt.LeftButton and modifiers == QtCore.Qt.ShiftModifier: self.current_action = ON_SELECT_AREA self.update() def wheelEvent(self, event): if self._have_pyqt5: delta = event.angleDelta().y() else: delta = event.delta() if delta > 0: self.zoom_factor = 1.3 else: self.zoom_factor = 0.7 self.current_action = ON_ZOOM_FACTOR self.point_on_mouse_move = event self.update() def keyPressEvent(self, event): if event.key() == ord("F"): # fit all command, invokes repaint self.current_action = ON_ZOOM_FITALL self.update() else: super(GLWidget, self).keyPressEvent(event) def on_zoom_area(self): dx, dy = self.delta_mouse_event_pos tolerance = 2 if abs(dx) <= tolerance and abs(dy) <= tolerance: # zooming at a near nil value can segfault the opengl viewer pass else: if not self.is_right_mouse_button_surpressed: coords = [self.point_on_mouse_press[0], self.point_on_mouse_press[1], self.point_on_mouse_move[0], self.point_on_mouse_move[1]] self._display.ZoomArea(*coords) def on_zoom_factor(self): self._display.ZoomFactor(self.zoom_factor) def on_zoom_fitall(self): """ handle fitting the scene in the viewport invoked on pressing "f" """ self._display.FitAll() def on_zoom(self): self._zoom_area = True def on_dyn_zoom(self): """ handle zooming of the viewport through the shift + right mouse button """ self._display.DynamicZoom(self.point_on_mouse_press[0], self.point_on_mouse_press[1], self.point_on_mouse_move[0], self.point_on_mouse_move[1] ) self.point_on_mouse_press = self._point_on_mouse_move def on_dyn_rot(self): """ handle rotation of the viewport """ self._display.StartRotation(*self.point_on_mouse_press) self._display.Rotation(*self.point_on_mouse_move) self.point_on_mouse_press = self._point_on_mouse_move def on_dyn_pan(self): """ handle panning of the viewport """ dx, dy = self.delta_mouse_event_pos self.point_on_mouse_press = self._point_on_mouse_move self._display.Pan(-dx, dy) def on_move_to(self): # Relays mouse position in pixels theXPix and theYPix to the # interactive context selectors # This is done by the view theView passing this position to the main # viewer and updating it # Functions in both Neutral Point and local contexts # TODO: 6.9.1 changes this, important... # If theToRedrawOnUpdate is set to false, callee should call # RedrawImmediate() to highlight detected object. print(" no specific action -> MoveTo") self._display.MoveTo(*self.point_on_mouse_move) def on_select(self): self._display.Select(*self.point_on_mouse_move) def on_shift_select(self): self._display.ShiftSelect(*self.point_on_mouse_move) def on_select_area(self): pass # TODO: not really implemented # self._display.SelectArea(Xmin, Ymin, Xmin+dx, Ymin+dy) def _dispatch_camera_command_actions(self): """ dispatches actions that involve zooming, panning or rotating the viewport. Any of these actions invokes redrawing the view. This is why its relevant that these are handled in a specific method This method is to be called **exclusisely** from the .paintEvent method since here it can be interwoven with the overpainting routines Returns ------- perform_action : bool True if any actions were performed, and implicitly the viewport was redrawn False otherwise """ perform_action = False if self.current_action: print("handling camera action:", self.current_action) try: if self.current_action is not None: action = getattr(self, self.current_action) action() except Exception: print("could not invoke camera command action {0}".format( self.current_action)) finally: self.current_action = None return perform_action def paintEvent(self, event): """ handles all actions that redraw the viewport Parameters ---------- event : QPaintEvent See Also -------- this method is also invoked through the self.update method, see the mouseMoveEvent method """ if self._inited: # actions like panning, zooming and rotating the view invoke # redrawing it # therefore, these actions need to be performed in the paintEvent # method # this way, redrawing the view takes place synchronous with the # overpaint action # not respecting this order would lead to a jittering view if not self._dispatch_camera_command_actions(): # if no camera actions took place, invoke a redraw of # the viewport self._display.View.Redraw() if self.context().isValid(): # acquire the OpenGL context self.makeCurrent() painter = QtGui.QPainter(self) painter.setRenderHint(QtGui.QPainter.Antialiasing, True) # swap the buffer before overpainting it self.swapBuffers() # perform overpainting self._overpaint(event, painter) painter.end() # hand over the OpenGL context self.doneCurrent() else: print('invalid OpenGL context: Qt cannot overpaint viewer') def _overpaint(self, event, painter): """ overpaint the viewport the viewport is overpainted to render a rectangle selection or -more awesomely- to overpaint bubbles on top of the viewport in a background rendering thread Parameters ---------- event: painter: """ self.drawBubbles(event, painter) # draw instructions in half transparent rectangle on top of # the viewport self.drawInstructions(painter) if self._drawbox: # draw selection rectangle self.drawBox(painter) def showEvent(self, event): """ invoked when on first draw of the viewport """ self.createBubbles(20 - len(self.bubbles)) def createBubbles(self, number): """ instantiate a `number` of bubbles to be painted on top of the viewport """ for _ in range(number): position = QtCore.QPointF( self.width() * (0.1 + 0.8 * random.random()), self.height() * (0.1 + 0.8 * random.random())) radius = min(self.width(), self.height()) * ( 0.0125 + 0.0875 * random.random()) velocity = QtCore.QPointF( self.width() * 0.0125 * (-0.5 + random.random()), self.height() * 0.0125 * (-0.5 + random.random())) self.bubbles.append(Bubble(position, radius, velocity)) def animate(self): """ update the position of the bubbles this method is invoked from the self.animationTimer method """ for bubble in self.bubbles: bubble.move(self.rect()) self.update() def drawBox(self, painter): """ overpaint a rectangle on top of the viewport, when selecting with Shift + right mouse button """ painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1)) tolerance = 2 dx, dy = self.delta_mouse_event_pos if abs(dx) <= tolerance and abs(dy) <= tolerance: pass else: rect = QtCore.QRect(self.point_on_mouse_press[0], self.point_on_mouse_press[1], -dx, -dy) painter.drawRect(rect) def drawBubbles(self, event, painter): """ overpaint soap like bubble on top of the viewport """ for bubble in self.bubbles: bubble_rect = bubble.rect() if bubble_rect.intersects(QtCore.QRectF(event.rect())): pt = QtCore.QPointF(self._point_on_mouse_move) over_mouse = bubble_rect.contains(pt) bubble.drawBubble(painter, over_mouse) def drawInstructions(self, painter): """ overpaint a message with a gray transparent background """ transparency = 80 metrics = QtGui.QFontMetrics(self.font()) border = max(4, metrics.leading()) rect = metrics.boundingRect(0, 0, self.width() - 2 * border, int(self.height() * 0.125), QtCore.Qt.AlignCenter | QtCore.Qt.TextWordWrap, self.text) painter.setRenderHint(QtGui.QPainter.TextAntialiasing) painter.fillRect( QtCore.QRect(0, 0, self.width(), rect.height() + 2 * border), QtGui.QColor(0, 0, 0, transparency)) painter.setPen(QtCore.Qt.white) painter.fillRect( QtCore.QRect(0, 0, self.width(), rect.height() + 2 * border), QtGui.QColor(0, 0, 0, transparency)) painter.drawText((self.width() - rect.width()) / 2, border, rect.width(), rect.height(), QtCore.Qt.AlignCenter | QtCore.Qt.TextWordWrap, self.text) if __name__ == '__main__': def TestOverPainting(): class AppFrame(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle(self.tr("qtDisplay3d overpainting example")) self.resize(1280, 1024) self.canva = GLWidget(self) mainLayout = QtWidgets.QHBoxLayout() mainLayout.addWidget(self.canva) mainLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(mainLayout) def runTests(self): self.canva._display.Test() app = QtWidgets.QApplication(sys.argv) frame = AppFrame() frame.show() frame.canva.InitDriver() frame.runTests() app.exec_() TestOverPainting()