def __init__(self, route, parent): QGraphicsScene.__init__(self, parent) self.route_points = route.routePoints() # len == legCount + 1 background_map_item = QGraphicsPixmapItem(QPixmap(world_map_pixmap)) rect = background_map_item.boundingRect() self._lon_factor = rect.width() / 360 self._lat_factor = -rect.height() / 180 background_map_item.setOffset(-rect.width() / 2, -rect.height() / 2) self.map_bounding_rect = background_map_item.boundingRect() self.addItem(background_map_item) self.point_items = [] for p in self.route_points: item = RoutePointItem() item.setPos(self.scenePoint(p.coordinates)) self.point_items.append(item) self.addItem(item) item = RoutePointCircleItem(3, route_colour_departure) item.setPos(self.scenePoint(self.route_points[0].coordinates)) self.addItem(item) item = RoutePointCircleItem(5, route_colour_arrival) item.setPos(self.scenePoint(self.route_points[-1].coordinates)) self.addItem(item) self.leg_items = [] for i in range(route.legCount()): item = RouteLegItem(self.route_points[i], self.route_points[i + 1]) self.leg_items.append(item) self.addItem(item)
def drawImage(self, image): height, width, dim = image.shape image = QImage(image, width, height, dim * width, QImage.Format_RGB888) image_item = QGraphicsPixmapItem(QPixmap.fromImage(image)) self.scene = QGraphicsScene() __width = image_item.boundingRect().width() __height = image_item.boundingRect().height() self.graphicsResize(__width, __height) self.scene.addItem(image_item) self.imageView.setScene(self.scene)
class Fan(QObject): """ Define a class to show a picture of a fan, for animation. To define a pyqtProperty for animation, the base class should be a QObject or any other inherited classes, like QWidget. Then, add a QGraphicsPixmapItem to host the picture. """ def __init__(self, parent=None): super().__init__(parent) self.pixmap_item = QGraphicsPixmapItem( QPixmap(cwd + '/guiunits/imags/pon56gdemo/fan.png')) self.pixmap_item.setTransformOriginPoint( self.pixmap_item.boundingRect().center()) #self.clickedSgnlWrapper = itemClickedSgnlWrapper() #self.clicked = self.clickedSgnlWrapper.sgnl #self.pixmap_item.mousePressEvent = self.clickEventHandler def clickEventHandler(self, event): print('emitting signal') self.clicked.emit() def _set_rotation_dgr(self, dgr): self.pixmap_item.setRotation(dgr) def fanAnimation(self): anim = QPropertyAnimation(self, b'rotation') anim.setDuration(1000) anim.setStartValue(0) anim.setEndValue(360) anim.setLoopCount(-1) return anim # define a property named as 'rotation', and designate a setter function. rotation = pyqtProperty(float, fset=_set_rotation_dgr)
def cloneBody(self, bodyspecName, dropPos, itemId=None, width=0): bodyDef = self.bodies[bodyspecName]; if not itemId: if bodyspecName not in self.nameIndex: self.nameIndex[bodyspecName] = 0; self.nameIndex[bodyspecName] += 1; itemId = "{}{}".format(bodyspecName, self.nameIndex[bodyspecName]); body = BodyItem(itemId, bodyspecName, 2); self.bodyInstances.append(body); body.setPos(dropPos); group = QGraphicsItemGroup(body); self.renderScene.addItem(body); width = width*self.UNITS_PER_METER or self.DEFAULT_BODY_SIZE; for shape in bodyDef["shapes"]: vertices = shape["vertices"]; if shape["type"] == "POLYGON": newItem = QGraphicsPolygonItem(QPolygonF(vertices)); if shape["type"] == "CIRCLE": p1, p2 = vertices radius = math.hypot(p2.x()-p1.x(), p2.y()-p1.y()); newItem = QGraphicsEllipseItem(p1.x()-radius, p1.y()-radius, radius*2, radius*2); pen = QPen(); pen.setWidth(0); newItem.setPen(pen); newItem.setParentItem(group); bounding = group.childrenBoundingRect(); imagePath = None; height = 0; if (bodyDef["image"]): imagePath = bodyDef["image"]; pixmap = QPixmap(imagePath); body.setPixmap(pixmap); pm = QGraphicsPixmapItem(pixmap.scaledToWidth(width), body); body.setImg(pm); pm.setFlags(QGraphicsItem.ItemStacksBehindParent); pm.setOffset(0, -pm.boundingRect().height()); group.setScale(width/self.TRANSCOORD_X); height = pm.boundingRect().height(); else: group.setScale(width/bounding.width()); height = bounding.height(); for item in body.childItems(): item.setPos(item.pos().x(), item.pos().y() + height) body.updateBorder(); return body;
def setImage(self, filePath: str) -> None: """ Convert `filaPath` to an instance of QPixmap and add it to the scene. """ pixmap = self.convertToPixmap(filePath) item = QGraphicsPixmapItem() item.setPixmap(pixmap) item.setTransformationMode(Qt.SmoothTransformation) self._update_scene(item, item.boundingRect())
class FuncItem(KineticsDisplayItem): name = constants.ITEM """Class for displaying Functions""" #fontMetrics = None font = QtGui.QFont(KineticsDisplayItem.defaultFontName) font.setPointSize(KineticsDisplayItem.defaultFontSize) fontMetrics = QtGui.QFontMetrics(font) def __init__(self, mobj, parent): super(FuncItem, self).__init__(mobj, parent) self.setFlag(QGraphicsItem.ItemIsMovable, True) iconmap_file = (os.path.join(config.settings[config.KEY_ICON_DIR], 'classIcon/Function.png')) self.funcImage = QtGui.QImage(iconmap_file).scaled(15, 33) self.bg = QGraphicsRectItem(self) self.bg.setAcceptHoverEvents(True) self.gobj = QGraphicsPixmapItem( QtGui.QPixmap.fromImage(self.funcImage), self.bg) self.gobj.setAcceptHoverEvents(True) self.gobj.mobj = self.mobj funcdoc = (moose.element(self.mobj.path)).expr self.gobj.setToolTip(funcdoc) def setDisplayProperties(self, x, y, textcolor, bgcolor): """Set the display properties of this item.""" poolt = ["ZombieBufPool", "BufPool", "ZombiePool", "Pool"] if self.gobj.mobj.parent.className in poolt: self.setGeometry(0, 30, self.gobj.boundingRect().width(), self.gobj.boundingRect().height()) else: self.setGeometry(x, y, self.gobj.boundingRect().width(), self.gobj.boundingRect().height()) self.bg.setBrush(QtGui.QBrush(bgcolor)) self.setFlag(QGraphicsItem.ItemIsMovable, False) def refresh(self, scale): pass def boundingRect(self): ''' reimplimenting boundingRect for redrawning ''' return QtCore.QRectF(0, 0, self.gobj.boundingRect().width(), self.gobj.boundingRect().height()) def updateSlot(self): return None def updateColor(self, bgcolor): return None def updateRect(self, ratio): return None def returnColor(self): return (self.bg.brush().color()) def updateValue(self, gobj, funcdoc='Not Available'): self.gobj.setToolTip(funcdoc)
class AnnotationScene(QGraphicsScene): def __init__(self, parent=None): super(AnnotationScene, self).__init__(parent) self.image_item = QGraphicsPixmapItem() self.image_item.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor)) self.image_item.setZValue(9) self.addItem(self.image_item) self.polygon_item = None def load_image(self, pixmap): self.image_item.setPixmap(pixmap) self.setSceneRect(self.image_item.boundingRect()) def setCurrentInstruction(self): self.polygon_item = PolygonAnnotation() self.addItem(self.polygon_item)
def __loadStaticImages(self, static_images): for image_info in static_images: pixmap = QPixmap(FileInfo().getPath(FileInfo.FileDataType.IMAGE, image_info.name)) height = image_info.height width = image_info.width if height is None: if width is not None: pixmap = pixmap.scaledToWidth(width) elif width is None: pixmap = pixmap.scaledToHeight(height) else: pixmap = pixmap.scaled(width, height) image_item = QGraphicsPixmapItem(pixmap) image_item.setPos(image_info.x, image_info.y) brect = image_item.boundingRect() image_item.setOffset(-brect.width() / 2, -brect.height() / 2) self.__ui.view.scene().addItem(image_item)
class LooseStripBayScene(QGraphicsScene): def __init__(self, parent): QGraphicsScene.__init__(self, parent) self.gui = parent self.bg_item = None self.compact_strips = False self.fillBackground() wref = 5 * loose_strip_width href = .75 * wref self.addRect(QRectF(-wref / 2, -href / 2, wref, href), pen=QPen(Qt.NoPen)) # avoid empty scene self.strip_items = EmptyGraphicsItem() self.addItem(self.strip_items) self.strip_items.setZValue(1) # gets strips on top of bg_item # External signal connections below. CAUTION: these must all be disconnected on widget deletion signals.selectionChanged.connect(self.updateSelection) signals.colourConfigReloaded.connect(self.fillBackground) env.strips.stripMoved.connect(self.removeInvisibleStripItems) def disconnectAllSignals(self): signals.selectionChanged.disconnect(self.updateSelection) signals.colourConfigReloaded.disconnect(self.fillBackground) env.strips.stripMoved.disconnect(self.removeInvisibleStripItems) def getStrips(self): return [item.strip for item in self.strip_items.childItems()] def fillBackground(self): self.setBackgroundBrush(settings.colour('loose_strip_bay_background')) def clearBgImg(self): if self.bg_item != None: self.removeItem(self.bg_item) self.bg_item = None def setBgImg(self, pixmap, scale): # pixmap None to clear background self.clearBgImg() self.bg_item = QGraphicsPixmapItem(pixmap, None) rect = self.bg_item.boundingRect() self.bg_item.setScale(scale * loose_strip_width / rect.width()) self.bg_item.setOffset(-rect.center()) self.addItem(self.bg_item) def setCompactStrips(self, b): self.compact_strips = b for item in self.strip_items.childItems(): item.setCompact(b) def updateSelection(self): for item in self.strip_items.childItems(): item.setZValue(1 if item.strip is selection.strip else 0) item.update() def placeNewStripItem(self, strip, pos): item = LooseStripItem(strip, self.compact_strips) item.setPos(pos) self.addStripItem(item) def deleteStripItem(self, strip): self.removeItem( next(item for item in self.strip_items.childItems() if item.strip is strip)) def removeInvisibleStripItems(self, strip): for item in self.strip_items.childItems(): if not item.isVisible(): self.removeItem(item) def deleteAllStripItems(self): for item in self.strip_items.childItems(): self.removeItem(item) def addStripItem(self, item): item.setParentItem(self.strip_items) def dropEvent(self, event): if event.mimeData().hasFormat(strip_mime_type): strip = env.strips.fromMimeDez(event.mimeData()) try: # maybe it was already inside this bay item = next(item for item in self.strip_items.childItems() if item.strip is strip) item.setPos(event.scenePos()) item.setVisible(True) except StopIteration: env.strips.repositionStrip(strip, None) self.placeNewStripItem(strip, event.scenePos()) signals.selectionChanged.emit() event.acceptProposedAction() def mousePressEvent(self, event): if self.mouseGrabberItem() == None: selection.deselect() event.accept() QGraphicsScene.mousePressEvent(self, event) def mouseDoubleClickEvent(self, event): QGraphicsScene.mouseDoubleClickEvent(self, event) if not event.isAccepted( ): # avoid creating when double clicking on a strip item event.accept() strip = new_strip_dialog(self.gui, None) if strip != None: self.placeNewStripItem(strip, event.scenePos()) selection.selectStrip(strip) def dragMoveEvent(self, event): pass # Scene's default impl. ignores the event when no item is under mouse (this enables mouse drop on scene)
def _load_pixmap(self, pixmap: QPixmap, reload_only: bool) -> None: """Load new pixmap into the graphics scene.""" item = QGraphicsPixmapItem() item.setPixmap(pixmap) item.setTransformationMode(Qt.SmoothTransformation) self._update_scene(item, item.boundingRect(), reload_only)
class GraphicsView(QGraphicsView): def __init__(self, parent=None): super(GraphicsView, self).__init__(parent) self.graphics_scene = QGraphicsScene() self.pixmap_item = QGraphicsPixmapItem() self.graphics_scene.addItem(self.pixmap_item) self.setScene(self.graphics_scene) self.image = None self.image_max = 255 self.image_range = 255 self.window_min, self.window_max = 0, 255 self.shift = 0 self.scroll = None self.current_language = 'en_GB' self.context_menu = QMenu() self.make_windowing_optional = False if self.make_windowing_optional: self.use_windowing_action = QAction( QCoreApplication.translate('MainWindow', 'Use Windowing')) self.use_windowing_action.setCheckable(True) self.use_windowing_action.setChecked(True) self.use_windowing_action.toggled.connect( self.on_use_windowing_action) self.context_menu.addAction(self.use_windowing_action) self.reset_window_action = QAction( QCoreApplication.translate('MainWindow', 'Reset Window')) self.reset_window_action.setCheckable(False) self.reset_window_action.triggered.connect(self.on_reset_window_action) self.context_menu.addAction(self.reset_window_action) def set_image(self, image, update_values=False): self.image = np.copy(image) if update_values: self.update_values_from_image(self.image) self.update() self.resizeEvent() def update_values_from_image(self, image): if image.min() < 0: self.shift = abs(image.min()) self.image_max = image.max() + self.shift self.image_range = image.max() - image.min() + self.shift self.window_min, self.window_max = image.min() + self.shift, image.max( ) + self.shift def update(self): if self.image is None: return image = np.interp(np.copy(self.image + self.shift), (self.window_min, self.window_max), (0, 255)).astype(np.uint8) self.graphics_scene.removeItem(self.graphics_scene.items()[0]) self.pixmap_item = QGraphicsPixmapItem(QPixmap(array2qimage(image))) self.graphics_scene.addItem(self.pixmap_item) def mouseMoveEvent(self, event): if self.make_windowing_optional and not self.use_windowing_action.isChecked( ): return window_center = self.image_max * event.pos().y() / self.height() half_window_width = 0.5 * self.image_range * event.pos().x( ) / self.width() self.window_min, self.window_max = window_center + half_window_width * np.array( [-1, 1]) self.update() def wheelEvent(self, event): if self.scroll is not None: delta = np.sign( event.angleDelta().y()) # * 0.01 * self.scroll.maximum() self.scroll.setValue(self.scroll.value() + delta) def resizeEvent(self, event=None): self.fitInView(self.pixmap_item.boundingRect(), Qt.KeepAspectRatio) def contextMenuEvent(self, event): self.context_menu.exec(event.globalPos()) def on_use_windowing_action(self): if not self.use_windowing_action.isChecked(): self.on_reset_window_action() def on_reset_window_action(self): self.update_values_from_image(self.image) self.update() def change_language(self, lang): self.reset_window_action.setText( QCoreApplication.translate('MainWindow', 'Reset Window')) if self.make_windowing_optional: self.use_windowing_action.setText( QCoreApplication.translate('MainWindow', 'Use Windowing'))
class ASCGraphicsView(QGraphicsView): scale_signal = pyqtSignal('float', 'float') set_focus_point_signal = pyqtSignal('int', 'int', 'int') set_focus_point_percent_signal = pyqtSignal('float', 'float', 'float') move_focus_point_signal = pyqtSignal('int', 'int', 'int') # x, y, z, BRUSH_TYPE, BRUSH_SIZE, ERASE paint_anno_on_point_signal = pyqtSignal('int', 'int', 'int', 'int', 'int', 'bool', 'bool') def __init__(self, parent=None): super(ASCGraphicsView, self).__init__(parent) self.scene = QGraphicsScene(self) self.raw_img_item = QGraphicsPixmapItem() self.raw_img_item.setZValue(0) self.anno_img_item = QGraphicsPixmapItem() self.anno_img_item.setZValue(1) self.cross_bar_v_line_item = QGraphicsLineItem() self.cross_bar_h_line_item = QGraphicsLineItem() self.cross_bar_v_line_item.setZValue(100) self.cross_bar_h_line_item.setZValue(100) self.cross_bar_v_line_item.setPen( QPen(Qt.blue, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin)) self.cross_bar_h_line_item.setPen( QPen(Qt.blue, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin)) self.paint_brush_circle_item = QGraphicsEllipseItem() self.paint_brush_rect_item = QGraphicsPolygonItem() self.paint_brush_circle_item.setZValue(10) self.paint_brush_rect_item.setZValue(11) self.paint_brush_circle_item.setVisible(False) self.paint_brush_rect_item.setVisible(False) self.paint_brush_circle_item.setPen( QPen(Qt.red, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin)) self.paint_brush_rect_item.setPen( QPen(Qt.red, 0, Qt.DotLine, Qt.FlatCap, Qt.RoundJoin)) self.scene.addItem(self.raw_img_item) self.scene.addItem(self.anno_img_item) self.scene.addItem(self.cross_bar_v_line_item) self.scene.addItem(self.cross_bar_h_line_item) self.scene.addItem(self.paint_brush_circle_item) self.scene.addItem(self.paint_brush_rect_item) self.setScene(self.scene) self.setViewport(QOpenGLWidget()) self._last_button_press = Qt.NoButton self._last_pos_middle_button = None self._last_pos_right_button = None self._brush_stats = {'type': BRUSH_TYPE_NO_BRUSH, 'size': 5} self.setResizeAnchor(QGraphicsView.AnchorViewCenter) self.slice_scroll_bar = None self.image_size = None self.is_valid = False def clear(self): """before loading new image""" self._last_pos_middle_button = None self._last_pos_right_button = None self._last_button_press = Qt.NoButton self.raw_img_item.setPixmap(QPixmap()) self.anno_img_item.setPixmap(QPixmap()) self.paint_brush_circle_item.setVisible(False) self.paint_brush_rect_item.setVisible(False) self.image_size = None self.is_valid = False def init_view(self, image_size): """after loading new image""" self.is_valid = True self.image_size = image_size self.slice_scroll_bar = self.parent().findChild( QScrollBar, self.objectName()[0] + 'SliceScrollBar') trans_mat = item2scene_transform[self.objectName()[0]] self.raw_img_item.setTransform(trans_mat) self.anno_img_item.setTransform(trans_mat) self.cross_bar_v_line_item.setTransform(trans_mat) self.cross_bar_h_line_item.setTransform(trans_mat) self.paint_brush_rect_item.setTransform(trans_mat) self.paint_brush_circle_item.setTransform(trans_mat) self.fitInView(self.raw_img_item, Qt.KeepAspectRatio) self.paint_brush_circle_item.setVisible(False) self.paint_brush_rect_item.setVisible(False) @property def brush_stats(self): return self._brush_stats @brush_stats.setter def brush_stats(self, stats_tuple): b_type, size = stats_tuple if b_type in [ BRUSH_TYPE_NO_BRUSH, BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH ]: self._brush_stats['type'] = b_type self._brush_stats['size'] = size if b_type != BRUSH_TYPE_NO_BRUSH: self.setMouseTracking(True) else: self.setMouseTracking(False) def update_brush_preview(self, x, y, out_of_sight=False): if not self.is_valid or self.brush_stats[ 'type'] == BRUSH_TYPE_NO_BRUSH or out_of_sight: self.paint_brush_rect_item.setVisible(False) self.paint_brush_circle_item.setVisible(False) return center = self.anno_img_item.mapFromScene(self.mapToScene(x, y)) start_x = self.raw_img_item.boundingRect().topLeft().x() start_y = self.raw_img_item.boundingRect().topLeft().y() end_x = self.raw_img_item.boundingRect().bottomRight().x() end_y = self.raw_img_item.boundingRect().bottomRight().y() center.setX(min(max(start_x, center.x()), end_x) + 0.5) center.setY(min(max(start_y, center.y()), end_y) + 0.5) top_left_x = int(center.x() - self.brush_stats['size'] / 2) top_left_y = int(center.y() - self.brush_stats['size'] / 2) rect = QRectF(top_left_x, top_left_y, self.brush_stats['size'], self.brush_stats['size']) if self.brush_stats['type'] == BRUSH_TYPE_CIRCLE_BRUSH: self.paint_brush_rect_item.setVisible(False) self.paint_brush_circle_item.setVisible(True) self.paint_brush_circle_item.setRect(rect) if self.brush_stats['type'] == BRUSH_TYPE_RECT_BRUSH: self.paint_brush_rect_item.setVisible(True) self.paint_brush_circle_item.setVisible(False) self.paint_brush_rect_item.setPolygon(QPolygonF(rect)) def anno_paint(self, x, y, erase=False, new_step=False): pos_on_item = self.raw_img_item.mapFromScene(self.mapToScene(x, y)) if self.objectName() == 'aGraphicsView': paint_point = [pos_on_item.y(), pos_on_item.x(), 999999] if self.objectName() == 'sGraphicsView': paint_point = [999999, pos_on_item.y(), pos_on_item.x()] if self.objectName() == 'cGraphicsView': paint_point = [pos_on_item.y(), 999999, pos_on_item.x()] self.paint_anno_on_point_signal.emit(math.floor(paint_point[0]), math.floor(paint_point[1]), math.floor(paint_point[2]), self.brush_stats['type'], self.brush_stats['size'], erase, new_step) @pyqtSlot('int') def on_slice_scroll_bar_changed(self, value): if not self.is_valid: return ratios = [-1, -1, -1] ratio = (value - self.slice_scroll_bar.minimum()) / \ (self.slice_scroll_bar.maximum() - self.slice_scroll_bar.minimum()) if self.objectName() == 'aGraphicsView': ratios[2] = ratio if self.objectName() == 'sGraphicsView': ratios[0] = ratio if self.objectName() == 'cGraphicsView': ratios[1] = ratio self.set_focus_point_percent_signal.emit(ratios[0], ratios[1], ratios[2]) @pyqtSlot('int', 'int') def set_brush_stats(self, b_type, size): self.brush_stats = [b_type, size] @pyqtSlot('int', 'int', 'int') def set_cross_bar(self, x, y, z): if self.objectName() == 'aGraphicsView': cross_bar_x = y cross_bar_y = x slice_bar_ratio = z / self.image_size[2] if self.objectName() == 'sGraphicsView': cross_bar_x = z cross_bar_y = y slice_bar_ratio = x / self.image_size[0] if self.objectName() == 'cGraphicsView': cross_bar_x = z cross_bar_y = x slice_bar_ratio = y / self.image_size[1] # cross line in voxel center cross_bar_x = cross_bar_x + 0.5 cross_bar_y = cross_bar_y + 0.5 start_x = self.raw_img_item.boundingRect().topLeft().x() start_y = self.raw_img_item.boundingRect().topLeft().y() end_x = self.raw_img_item.boundingRect().bottomRight().x() end_y = self.raw_img_item.boundingRect().bottomRight().y() self.cross_bar_v_line_item.setLine(cross_bar_x, start_y, cross_bar_x, end_y) self.cross_bar_h_line_item.setLine(start_x, cross_bar_y, end_x, cross_bar_y) slice_bar_value = round(slice_bar_ratio * (self.slice_scroll_bar.maximum() - self.slice_scroll_bar.minimum())) \ + self.slice_scroll_bar.minimum() self.slice_scroll_bar.setValue(slice_bar_value) def mousePressEvent(self, event: QtGui.QMouseEvent): self._last_button_press = event.button() if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH: if event.button() == Qt.LeftButton: item_coord_pos = self.raw_img_item.mapFromScene( self.mapToScene(event.pos())) if self.objectName() == 'aGraphicsView': new_focus_point = [ item_coord_pos.y(), item_coord_pos.x(), 999999 ] if self.objectName() == 'sGraphicsView': new_focus_point = [ 999999, item_coord_pos.y(), item_coord_pos.x() ] if self.objectName() == 'cGraphicsView': new_focus_point = [ item_coord_pos.y(), 999999, item_coord_pos.x() ] self.set_focus_point_signal.emit( math.floor(new_focus_point[0]), math.floor(new_focus_point[1]), math.floor(new_focus_point[2])) elif event.button() == Qt.MiddleButton: self._last_pos_middle_button = event.pos() self.setCursor(Qt.ClosedHandCursor) elif event.button() == Qt.RightButton: self._last_pos_right_button = event.pos() else: super(ASCGraphicsView, self).mousePressEvent(event) if self.brush_stats['type'] in [ BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH ]: if event.button() == Qt.LeftButton: self.anno_paint(event.x(), event.y(), erase=False, new_step=True) elif event.button() == Qt.MiddleButton: self._last_pos_middle_button = event.pos() self.setCursor(Qt.ClosedHandCursor) elif event.button() == Qt.RightButton: self.anno_paint(event.x(), event.y(), erase=True, new_step=True) def mouseMoveEvent(self, event: QtGui.QMouseEvent): if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH: if self._last_button_press == Qt.LeftButton: item_coord_pos = self.raw_img_item.mapFromScene( self.mapToScene(event.pos())) if self.objectName() == 'aGraphicsView': new_focus_point = [ item_coord_pos.y(), item_coord_pos.x(), 999999 ] if self.objectName() == 'sGraphicsView': new_focus_point = [ 999999, item_coord_pos.y(), item_coord_pos.x() ] if self.objectName() == 'cGraphicsView': new_focus_point = [ item_coord_pos.y(), 999999, item_coord_pos.x() ] self.set_focus_point_signal.emit( math.floor(new_focus_point[0]), math.floor(new_focus_point[1]), math.floor(new_focus_point[2])) elif self._last_button_press == Qt.MiddleButton: delta_x = event.x() - self._last_pos_middle_button.x() delta_y = event.y() - self._last_pos_middle_button.y() self.horizontalScrollBar().setValue( self.horizontalScrollBar().value() - delta_x) self.verticalScrollBar().setValue( self.verticalScrollBar().value() - delta_y) self._last_pos_middle_button = event.pos() elif self._last_button_press == Qt.RightButton: delta = event.pos().y() - self._last_pos_right_button.y() scale = 1 - float(delta) / float(self.size().height()) self.scale_signal.emit(scale, scale) self._last_pos_right_button = event.pos() else: super(ASCGraphicsView, self).mouseMoveEvent(event) if self.brush_stats['type'] in [ BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH ]: self.update_brush_preview(event.x(), event.y()) if self._last_button_press == Qt.LeftButton: self.anno_paint(event.x(), event.y(), erase=False, new_step=False) elif self._last_button_press == Qt.MiddleButton: delta_x = event.x() - self._last_pos_middle_button.x() delta_y = event.y() - self._last_pos_middle_button.y() self.horizontalScrollBar().setValue( self.horizontalScrollBar().value() - delta_x) self.verticalScrollBar().setValue( self.verticalScrollBar().value() - delta_y) self._last_pos_middle_button = event.pos() elif self._last_button_press == Qt.RightButton: self.anno_paint(event.x(), event.y(), erase=True, new_step=False) def mouseReleaseEvent(self, event: QtGui.QMouseEvent): self._last_button_press = Qt.NoButton if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH: if event.button() == Qt.MiddleButton: self.setCursor(Qt.ArrowCursor) if self.brush_stats['type'] in [ BRUSH_TYPE_CIRCLE_BRUSH, BRUSH_TYPE_RECT_BRUSH ]: if event.button() == Qt.LeftButton: pass elif event.button() == Qt.RightButton: pass else: super(ASCGraphicsView, self).mouseReleaseEvent(event) def wheelEvent(self, event: QtGui.QWheelEvent): # super(ASCGraphicsView, self).wheelEvent(event) if self.objectName() == 'aGraphicsView': if event.angleDelta().y() > 0: self.move_focus_point_signal.emit(0, 0, -1) elif event.angleDelta().y() < 0: self.move_focus_point_signal.emit(0, 0, 1) if self.objectName() == 'sGraphicsView': if event.angleDelta().y() > 0: self.move_focus_point_signal.emit(-1, 0, 0) elif event.angleDelta().y() < 0: self.move_focus_point_signal.emit(1, 0, 0) if self.objectName() == 'cGraphicsView': if event.angleDelta().y() > 0: self.move_focus_point_signal.emit(0, -1, 0) elif event.angleDelta().y() < 0: self.move_focus_point_signal.emit(0, 1, 0) def leaveEvent(self, event: QtCore.QEvent): self._last_button_press = Qt.NoButton if self.brush_stats['type'] == BRUSH_TYPE_NO_BRUSH: super(ASCGraphicsView, self).leaveEvent(event) else: self.update_brush_preview(0, 0, out_of_sight=True)
class GameArea(QWidget): #constants of widget RATIO_WITH_SHIPLIST = 11 / 14 RATIO_WITHOUT_SHIPLIST = 1 EPS = 0.3 #signals shipPlaced = pyqtSignal(Ship) def __init__(self, parent=None): super(GameArea, self).__init__(parent) self.__ui = Ui_GameArea() self.__ui.setupUi(self) self.__ratio = self.RATIO_WITH_SHIPLIST self.__scaleFactor = 1 self.controller = Controller() self.controller._accept = self.__accept self.controller._decline = self.__decline self.__gameModel = None self.__shipList = { "boat": ShipListItem(length=1, name="boat", count=4), "destroyer": ShipListItem(length=2, name="destroyer", count=3), "cruiser": ShipListItem(length=3, name="cruiser", count=2), "battleship": ShipListItem(length=4, name="battleship", count=1), } # resources self.__cellImages = {"intact": None, "hit": None, "miss": None} self.__sprites = {"explosion": None, "splash": None} self.__originalTileSize = 0 self.tileSize = 0 self.__shipListImage = QImage() self.__counterImage = QImage() # drawing items self.__placedShips = [] self.__field = [QGraphicsPixmapItem() for _ in range(100)] self.__letters = [QGraphicsTextItem() for _ in range(10)] self.__numbers = [QGraphicsTextItem() for _ in range(10)] for i in range(10): self.__letters[i].setDefaultTextColor(QColor.fromRgb(0, 0, 0)) self.__numbers[i].setDefaultTextColor(QColor.fromRgb(0, 0, 0)) self.__spriteAnimations = [] self.__shipListItem = QGraphicsPixmapItem() self.__ghostShip = QGraphicsPixmapItem( ) # data: 0 - rotation; 1 - ShipListItem self.__placer = QGraphicsRectItem() self.__dragShip = False self.__targetPixmap = None self.__targets = [] self.__scanEffect = None # prepare Qt objects self.__scene = QGraphicsScene() self.__loadResources() self.__initGraphicsView() self.__adjustedToSize = 0 def serviceModel(self, game_model): self.removeModel() self.__gameModel = game_model self.__gameModel.shipKilled.connect(self.__shootAround) def removeModel(self): if self.__gameModel: self.shipKilled.disconnect() self.__gameModel = None def __loadResources(self): if DEBUG_RESOURCE: resourcesPath = os.path.join(os.path.dirname(__file__), DEBUG_RESOURCE) else: resourcesPath = Environment.Resources.path() first = True for imageName in self.__cellImages: image = QImage( os.path.join(resourcesPath, "img", "cells", f"{imageName}.png")) if first: first = False self.__originalTileSize = min(image.width(), image.height()) else: self.__originalTileSize = min(self.__originalTileSize, image.width(), image.height()) self.__cellImages[imageName] = image for spriteName in self.__sprites: image = QImage( os.path.join(resourcesPath, "img", "cells", f"{spriteName}.png")) self.__sprites[spriteName] = image for shipName, ship in self.__shipList.items(): ship.image = QImage( os.path.join(resourcesPath, "img", "ships", f"{shipName}.png")) self.__shipListImage = QImage( os.path.join(resourcesPath, "img", "backgrounds", "shiplist.png")) self.__counterImage = QImage( os.path.join(resourcesPath, "img", "miscellaneous", "shipcounter.png")) self.__targetPixmap = QPixmap( os.path.join(resourcesPath, "img", "cells", "target.png")) def __initGraphicsView(self): self.__ui.graphicsView.setScene(self.__scene) self.__ui.graphicsView.viewport().installEventFilter(self) self.__ui.graphicsView.setRenderHints( QPainter.RenderHint.HighQualityAntialiasing | QPainter.RenderHint.TextAntialiasing | QPainter.RenderHint.SmoothPixmapTransform) self.__ui.graphicsView.horizontalScrollBar().blockSignals(True) self.__ui.graphicsView.verticalScrollBar().blockSignals(True) for cell in self.__field: cell.setData(0, "intact") pixmap = QPixmap.fromImage(self.__cellImages["intact"]) cell.setPixmap(pixmap) cell.setTransformationMode( Qt.TransformationMode.SmoothTransformation) self.__scene.addItem(cell) ca_letters = ["А", "Б", "В", "Г", "Д", "Е", "Ж", "З", "И", "К"] # ca_letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] ca_numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] font = QFont("Roboto") font.setPixelSize(int(self.__originalTileSize * 0.8)) for i in range(10): self.__letters[i].setPlainText(ca_letters[i]) self.__letters[i].setFont(font) self.__numbers[i].setPlainText(ca_numbers[i]) self.__numbers[i].setFont(font) self.__scene.addItem(self.__letters[i]) self.__scene.addItem(self.__numbers[i]) self.__shipListItem.setPixmap(QPixmap.fromImage(self.__shipListImage)) self.__shipListItem.setTransformationMode( Qt.TransformationMode.SmoothTransformation) self.__scene.addItem(self.__shipListItem) font.setPixelSize(int(self.__originalTileSize * 0.3)) for _, ship in self.__shipList.items(): t = QTransform().rotate(90) ship.shipItem.setPixmap( QPixmap.fromImage(ship.image).transformed(t)) ship.shipItem.setTransformationMode( Qt.TransformationMode.SmoothTransformation) ship.counterItem.setPixmap(QPixmap.fromImage(self.__counterImage)) ship.counterItem.setTransformationMode( Qt.TransformationMode.SmoothTransformation) ship.counterText.setPlainText(str(ship.count)) ship.counterText.setFont(font) self.__scene.addItem(ship.shipItem) self.__scene.addItem(ship.counterItem) self.__scene.addItem(ship.counterText) self.__ghostShip.setTransformationMode( Qt.TransformationMode.SmoothTransformation) self.__ghostShip.setOpacity(0.7) pen = QPen() pen.setWidth(2) pen.setStyle(Qt.PenStyle.DashLine) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) self.__placer.setPen(pen) def __setCell(self, x, y, cell_type): if cell_type not in self.__cellImages.keys(): raise ValueError( f"Type is {cell_type}. Allowed \"intact\", \"miss\", \"hit\"") cellItem = self.__field[y * 10 + x] cellItem.setData(0, cell_type) pixmap = QPixmap.fromImage(self.__cellImages[cell_type]) cellItem.setPixmap(pixmap) def __runAnimation(self, x, y, animation, looped=False): sprite = SpriteItem() sprite.setData(0, QPoint(x, y)) spritePixmap = QPixmap.fromImage(self.__sprites[animation]) sprite.setSpriteMap(spritePixmap, 60, 60, 5) sprite.setScale(self.__scaleFactor) sprite.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) sprite.setZValue(10) self.__spriteAnimations.append(sprite) self.__scene.addItem(sprite) def removeAnimation(): self.__scene.removeItem(sprite) self.__spriteAnimations.remove(sprite) sprite.stopAnimation() sprite.startAnimation(100, looped, removeAnimation) def __shootAround(self, ship: Ship): shipCells = [] for i in range(ship.length): shipCells.append(ship.pos + (QPoint(0, i) if ship.vertical else QPoint(i, 0))) cells = [] for cell in shipCells: arounds = [ cell + QPoint(i, j) for i in range(-1, 2) for j in range(-1, 2) ] for candidate in arounds: if 0 <= candidate.x() < 10 and 0 <= candidate.y() < 10: if candidate not in cells: cells.append(candidate) for cell in cells: if cell not in shipCells: self.__setCell(cell.x(), cell.y(), "miss") self.__runAnimation(cell.x(), cell.y(), "splash") log.debug( f"name: {ship.name} | length: {ship.length} | pos: {ship.pos} | vertical: {ship.vertical}" ) def hasHeightForWidth(self): return True def heightForWidth(self, width): return width / self.__ratio def hideShipList(self): self.__ratio = self.RATIO_WITHOUT_SHIPLIST self.__scene.removeItem(self.__shipListItem) for _, ship in self.__shipList.items(): self.__scene.removeItem(ship.shipItem) self.__adjustedToSize = None resize = QResizeEvent(self.size(), self.size()) QApplication.postEvent(self, resize) def getPlacedShips(self): return self.__placedShips def placedShipsCount(self): return len(self.__placedShips) def hideShips(self): for ship in self.__placedShips: self.__scene.removeItem(ship) def removePlacedShips(self): if self.__shipListItem.scene() is None: return for ship in self.__placedShips: shipListItem = ship.data(1) shipListItem.count += 1 shipListItem.counterText.setPlainText(str(shipListItem.count)) self.__scene.removeItem(ship) self.__placedShips.clear() def shuffleShips(self): if self.__shipListItem.scene() is None: return self.removePlacedShips() def findPossibleCells(length, rotation): vertical = rotation.isVertical() if vertical == rotation.isHorizontal(): raise Exception( "Unknown state! Rotation is not horizontal and not vertical." ) # wtf width = 1 if vertical else length height = length if vertical else 1 cells = [] for x in range(10): for y in range(10): if QRect(0, 0, 10, 10) != QRect(0, 0, 10, 10).united( QRect(x, y, width, height)): break if self.__validatePosition(x, y, width, height): cells.append(QPoint(x, y)) return cells shipList = list(self.__shipList.values()) shipList.sort(key=lambda ship: ship.length, reverse=True) for shipItem in shipList: for i in range(shipItem.count): rot = random.choice(list(Rotation)) cells = findPossibleCells(shipItem.length, rot) if not cells: rot = rot.next() cells = findPossibleCells(shipItem.length, rot) if not cells: return cell = random.choice(cells) self.__placeShip(shipItem, cell.x(), cell.y(), rot) def resizeEvent(self, event): size = event.size() if size == self.__adjustedToSize: return self.__adjustedToSize = size nowWidth = size.width() nowHeight = size.height() width = min(nowWidth, nowHeight * self.__ratio) height = min(nowHeight, nowWidth / self.__ratio) h_margin = round((nowWidth - width) / 2) - 2 v_margin = round((nowHeight - height) / 2) - 2 self.setContentsMargins( QMargins(h_margin, v_margin, h_margin, v_margin)) self.__resizeScene() def scanEffect(self, x, y): """ :return: True on success, False otherwise """ if self.__scanEffect: return False def scanFinisedCallback(): self.__scene.removeItem(self.__scanEffect) del self.__scanEffect self.__scanEffect = None target = FadingPixmapItem(self.__targetPixmap) target.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) target.setScale(self.__scaleFactor) target.setData(0, (x, y)) self.__targets.append(target) self.__scene.addItem(target) self.__scanEffect = FloatingGradientItem( QRectF(self.tileSize, self.tileSize, self.__originalTileSize * 10, self.__originalTileSize * 10), scanFinisedCallback) self.__scanEffect.setScale(self.__scaleFactor) self.__scanEffect.setBackwards(True) self.__scene.addItem(self.__scanEffect) return True def __resizeScene(self): width = self.__ui.graphicsView.width() height = self.__ui.graphicsView.height() self.__scene.setSceneRect(0, 0, width, height) self.tileSize = min(width / 11, height / (11 / self.__ratio)) self.__scaleFactor = self.tileSize / self.__originalTileSize for i, cell in enumerate(self.__field): x = i % 10 y = i // 10 cell.setScale(self.__scaleFactor) cell.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) for sprite in self.__spriteAnimations: pos = sprite.data(0) sprite.setPos((pos.x() + 1) * self.tileSize, (pos.y() + 1) * self.tileSize) sprite.setScale(self.__scaleFactor) for i in range(10): letter = self.__letters[i] letter.setScale(self.__scaleFactor) offsetX = (self.tileSize - letter.boundingRect().width() * self.__scaleFactor) / 2 offsetY = (self.tileSize - letter.boundingRect().height() * self.__scaleFactor) / 2 letter.setPos((i + 1) * self.tileSize + offsetX, offsetY) number = self.__numbers[i] number.setScale(self.__scaleFactor) offsetX = (self.tileSize - number.boundingRect().width() * self.__scaleFactor) / 2 offsetY = (self.tileSize - number.boundingRect().height() * self.__scaleFactor) / 2 number.setPos(offsetX, (i + 1) * self.tileSize + offsetY) xPos = 0 for _, ship in self.__shipList.items(): xPos += (ship.length - 1) xOffset = xPos * self.tileSize ship.shipItem.setScale(self.__scaleFactor) ship.shipItem.setPos(self.tileSize + xOffset, self.tileSize * (12 + self.EPS)) ship.counterItem.setScale(self.__scaleFactor) ship.counterItem.setPos(self.tileSize + xOffset, self.tileSize * (12.65 + self.EPS)) counterSize = ship.counterItem.boundingRect() textSize = ship.counterText.boundingRect() textXOffset = (counterSize.width() - textSize.width()) * self.__scaleFactor / 2 textYOffset = (counterSize.height() - textSize.height()) * self.__scaleFactor / 2 textX = ship.counterItem.pos().x() + textXOffset textY = ship.counterItem.pos().y() + textYOffset ship.counterText.setScale(self.__scaleFactor) ship.counterText.setPos(textX, textY) for ship in self.__placedShips: mapPos = ship.data(2) ship.setPos((mapPos.x() + 1) * self.tileSize, (mapPos.y() + 1) * self.tileSize) ship.setScale(self.__scaleFactor) for target in self.__targets: x, y = target.data(0) target.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) target.setScale(self.__scaleFactor) if self.__scanEffect: self.__scanEffect.setPos(self.tileSize, self.tileSize) self.__scanEffect.setScale(self.__scaleFactor) shipListX = self.tileSize shipListY = self.tileSize * (11 + self.EPS) self.__shipListItem.setScale(self.__scaleFactor) self.__shipListItem.setPos(shipListX, shipListY) self.__ghostShip.setScale(self.__scaleFactor) def eventFilter(self, obj, event): if obj is self.__ui.graphicsView.viewport(): if event.type() == QEvent.MouseButtonPress: self.__viewportMousePressEvent(event) elif event.type() == QEvent.MouseButtonRelease: self.__viewportMouseReleaseEvent(event) elif event.type() == QEvent.MouseMove: self.__viewportMouseMoveEvent(event) return super().eventFilter(obj, event) def sceneToMap(self, x, y): x -= self.tileSize y -= self.tileSize x //= self.tileSize y //= self.tileSize return int(x), int(y) def __initGhostShip(self, ship, pos): self.__ghostShip.setPixmap(QPixmap.fromImage(ship.image)) self.__ghostShip.setPos(pos) self.__ghostShip.setRotation(0) width = self.__ghostShip.boundingRect().width() height = self.__ghostShip.boundingRect().height() self.__ghostShip.setOffset(-width / 2, -height / 2) self.__ghostShip.setData(0, Rotation.UP) self.__ghostShip.setData(1, ship) self.__placer.setRect(0, 0, self.tileSize, self.tileSize * ship.length) self.__placer.setZValue(50) self.__scene.addItem(self.__ghostShip) self.__ghostShip.setZValue(100) def __rotateGhostShip(self, rotation=None): rotation = rotation if rotation else self.__ghostShip.data(0).next() self.__ghostShip.setData(0, rotation) self.__ghostShip.setRotation(rotation.value) placerRect = self.__placer.rect() maxSide = max(placerRect.width(), placerRect.height()) minSide = min(placerRect.width(), placerRect.height()) if rotation.isHorizontal(): self.__placer.setRect(0, 0, maxSide, minSide) elif rotation.isVertical(): self.__placer.setRect(0, 0, minSide, maxSide) else: raise Exception( "Unknown state! Rotation is not horizontal and not vertical." ) # wtf self.__validatePlacer() def __ghostShipLongSurface(self): pos = self.__ghostShip.pos() x = pos.x() y = pos.y() rot: Rotation = self.__ghostShip.data(0) length = self.__ghostShip.data(1).length if rot.isHorizontal(): x -= self.tileSize * length / 2 return x, y if rot.isVertical(): y -= self.tileSize * length / 2 return x, y def __validatePosition(self, x, y, width=1, height=1): positionRect = QRectF((x + 1) * self.tileSize, (y + 1) * self.tileSize, self.tileSize * width, self.tileSize * height) isPlacerValid = True for ship in self.__placedShips: shipRect = ship.mapRectToScene(ship.boundingRect()).adjusted( -self.tileSize / 2, -self.tileSize / 2, self.tileSize / 2, self.tileSize / 2) isPlacerValid = not positionRect.intersects(shipRect) if not isPlacerValid: break return isPlacerValid def __validatePlacer(self): sceneX, sceneY = self.__ghostShipLongSurface() x, y = self.sceneToMap(sceneX, sceneY) self.__placer.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) permittedArea = QRectF(self.__ui.graphicsView.viewport().geometry()) permittedArea.setTopLeft(QPointF(self.tileSize, self.tileSize)) permittedArea.setBottomRight( QPointF(self.tileSize * 12, self.tileSize * 12)) placerSize = self.__placer.boundingRect() placerRect = QRectF(sceneX, sceneY, placerSize.width(), placerSize.height()) isPlacerValid = False # first validation - ship can be placed inside game field if permittedArea.contains(self.__ghostShip.pos( )) and permittedArea == permittedArea.united(placerRect): if self.__placer.scene() == None: self.__scene.addItem(self.__placer) x, y = self.sceneToMap(sceneX, sceneY) self.__placer.setPos((x + 1) * self.tileSize, (y + 1) * self.tileSize) isPlacerValid = True else: if self.__placer.scene() == self.__scene: self.__scene.removeItem(self.__placer) # second validation - placer does not intersect with other ships if isPlacerValid: isPlacerValid = self.__validatePosition( x, y, placerSize.width() / self.tileSize, placerSize.height() / self.tileSize) # set color of placer pen = self.__placer.pen() if isPlacerValid: pen.setColor(Qt.GlobalColor.darkGreen) else: pen.setColor(Qt.GlobalColor.red) self.__placer.setPen(pen) self.__placer.setData(0, isPlacerValid) def __placeShip(self, shipListItem, x, y, rotation): sceneX, sceneY = (x + 1) * self.tileSize, (y + 1) * self.tileSize shipListItem.count -= 1 shipListItem.counterText.setPlainText(str(shipListItem.count)) pixmap = QPixmap(shipListItem.image).transformed(QTransform().rotate( (rotation.value))) placedShip = QGraphicsPixmapItem(pixmap) placedShip.setData(0, rotation) placedShip.setData(1, shipListItem) placedShip.setData(2, QPoint(x, y)) # position in map coordinates placedShip.setPos(sceneX, sceneY) placedShip.setTransformationMode( Qt.TransformationMode.SmoothTransformation) placedShip.setScale(self.__scaleFactor) self.__placedShips.append(placedShip) self.__scene.addItem(placedShip) def __placeGhostShip(self): isPlacingPermitted = self.__placer.data(0) if isPlacingPermitted: sceneX = self.__placer.pos().x() + self.tileSize / 2 sceneY = self.__placer.pos().y() + self.tileSize / 2 mapX, mapY = self.sceneToMap(sceneX, sceneY) rotation = self.__ghostShip.data(0) if rotation.isVertical(): vertical = True elif rotation.isHorizontal(): vertical = False else: raise Exception( "Unknown state! Rotation is not horizontal and not vertical." ) # wtf shipListItem = self.__ghostShip.data(1) self.__placeShip(shipListItem, mapX, mapY, rotation) log.debug( f"ship \"{shipListItem.name}\"; " f"position ({mapX}, {mapY}); " f"oriented {'vertically' if vertical else 'horizontally'}") self.shipPlaced.emit( Ship(name=shipListItem.name, length=shipListItem.length, pos=QPoint(mapX, mapY), vertical=vertical)) def __viewportMousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: if self.__shipListItem.scene(): # check press on shiplist shipUnderMouse = None for _, ship in self.__shipList.items(): if ship.shipItem.isUnderMouse(): shipUnderMouse = ship break # check press on field rotation = Rotation.RIGHT if shipUnderMouse == None: for ship in self.__placedShips: if ship.isUnderMouse(): rotation = ship.data(0) shipListItem = ship.data(1) if shipListItem.count == 0: shipListItem.shipItem.setGraphicsEffect(None) shipListItem.count += 1 shipListItem.counterText.setPlainText( str(shipListItem.count)) shipUnderMouse = shipListItem self.__placedShips.remove(ship) self.__scene.removeItem(ship) break # if ship grabbed if shipUnderMouse and shipUnderMouse.count > 0: self.__initGhostShip(shipUnderMouse, event.pos()) self.__rotateGhostShip(rotation) self.__dragShip = True x, y = self.sceneToMap(event.pos().x(), event.pos().y()) if x >= 0 and x < 10 and y >= 0 and y < 10: if self.controller.isBot: return self.controller.emitHit(x, y) if event.button() == Qt.MouseButton.RightButton: if self.__dragShip: self.__rotateGhostShip() def __viewportMouseReleaseEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: if self.__dragShip: self.__scene.removeItem(self.__ghostShip) self.__dragShip = False if self.__placer.scene() != None: self.__scene.removeItem(self.__placer) # self.__placeShip() self.__placeGhostShip() self.__placer.setData(0, False) def __viewportMouseMoveEvent(self, event): if self.__dragShip: self.__ghostShip.setPos(event.pos()) permittedArea = QRectF( self.__ui.graphicsView.viewport().geometry()) permittedArea.setTopLeft(QPointF(self.tileSize, self.tileSize)) # stop dragging ship if mouse out of field # if not permittedArea.contains(event.pos()): # self.__scene.removeItem(self.__ghostShip) # self.__dragShip = False self.__validatePlacer() def __accept(self, x, y, hit_type: CellState): log.debug( f" -- ACCEPTED -- hit on point ({x}, {y}) hit type: {hit_type}") cell = self.__field[y * 10 + x] if cell.data(0) != "intact": return if hit_type in [CellState.HIT, CellState.KILLED]: self.__setCell(x, y, 'hit') self.__runAnimation(x, y, "explosion", looped=True) for target in self.__targets: if (x, y) == target.data(0): self.__scene.removeItem(target) self.__targets.remove(target) break elif hit_type in [CellState.MISS]: self.__setCell(x, y, 'miss') self.__runAnimation(x, y, "splash", looped=False) if hit_type == CellState.KILLED: for ship in self.__placedShips: rotation = ship.data(0) length = ship.data(1).length position = ship.data(2) width = 1 if rotation.isVertical() else length height = length if rotation.isVertical() else 1 rect = QRect(position.x(), position.y(), width, height) if rect.contains(x, y): self.__scene.addItem(ship) def __decline(self, x, y): log.debug(f"declined hit on point ({x}, {y})")
class Robot(QGraphicsItemGroup): def __init__(self, mapSize, parent, repr): QGraphicsItemGroup.__init__(self) # 公有属性 self.gamePlace = 1 # 私有属性 self.__mapSize = mapSize self.__parent = parent self.__health = 100 self.__repr = repr self.__gunLock = "free" self.__radarLock = "Free" # 字面意思是雷达锁 # 两个动画对象、一个physics对象 self.__runAnimation = animation("run") self.__targetAnimation = animation("target") self.__physics = physics(self.__runAnimation) # 一个physics对象 # 设置一些颜色 self.maskColor = QColor(0, 255, 255) self.gunMaskColor = QColor(0, 255, 255) # 炮膛颜色 self.radarMaskColor = QColor(0, 255, 255) # 雷达颜色 # 加载车身图片 self.__base = QGraphicsPixmapItem() self.__base.pixmap = QPixmap(os.getcwd() + "/robotImages/baseGrey.png") self.__base.setPixmap(self.__base.pixmap) self.addToGroup(self.__base) self.__baseWidth = self.__base.boundingRect().width() self.__baseHeight = self.__base.boundingRect().height() # 加载炮膛图片 self.__gun = QGraphicsPixmapItem() self.__gun.pixmap = QPixmap(os.getcwd() + "/robotImages/gunGrey.png") self.__gun.setPixmap(self.__gun.pixmap) self.addToGroup(self.__gun) self.__gunWidth = self.__gun.boundingRect().width() self.__gunHeight = self.__gun.boundingRect().height() # 炮膛位置 x = self.__base.boundingRect().center().x() y = self.__base.boundingRect().center().y() self.__gun.setPos(x - self.__gunWidth / 2.0, y - self.__gunHeight / 2.0) # --------------------------雷达初始化----------------------------- # 加载雷达图片 self.__radar = QGraphicsPixmapItem() self.__radar.pixmap = QPixmap(os.getcwd() + "/robotImages/radar.png") self.__radar.setPixmap(self.__radar.pixmap) self.addToGroup(self.__radar) self.__radarWidth = self.__radar.boundingRect().width() self.__radarHeight = self.__radar.boundingRect().height() # radar position self.__radar.setPos(x - self.__radarWidth / 2.0, y - self.__radarHeight / 2.0) # 加载雷达范围 firstPoint = QPointF(x - self.__radarWidth / 2, y) secondPoint = QPointF(x + self.__radarWidth / 2, y) thirdPoint = QPointF(x + 4 * self.__radarWidth, y + 700) fourthPoint = QPointF(x - 4 * self.__radarWidth, y + 700) qPointListe = [] qPointListe.append(firstPoint) qPointListe.append(secondPoint) qPointListe.append(thirdPoint) qPointListe.append(fourthPoint) self.__radarField = radarField(qPointListe, self, "poly") # 大雷达范围 qPointListe.remove(fourthPoint) qPointListe.remove(thirdPoint) thirdPoint = QPointF(x + 10 * self.__radarWidth, y + 400) fourthPoint = QPointF(x - 10 * self.__radarWidth, y + 400) qPointListe.append(thirdPoint) qPointListe.append(fourthPoint) self.__largeRadarField = radarField(qPointListe, self, "poly") # 细雷达范围 qPointListe.remove(fourthPoint) qPointListe.remove(thirdPoint) thirdPoint = QPointF(x + 0.4 * self.__radarWidth, y + 900) fourthPoint = QPointF(x - 0.4 * self.__radarWidth, y + 900) qPointListe.append(thirdPoint) qPointListe.append(fourthPoint) self.__thinRadarField = radarField(qPointListe, self, "poly") # 弧形雷达范围 self.__roundRadarField = radarField([0, 0, 300, 300], self, "round") self.addToGroup(self.__roundRadarField) self.__roundRadarField.setPos( x - self.__roundRadarField.boundingRect().width() / 2.0, y - self.__roundRadarField.boundingRect().height() / 2.0) # add to group self.addToGroup(self.__radarField) self.addToGroup(self.__largeRadarField) self.addToGroup(self.__thinRadarField) # 一个坦克上有三个雷达范围,但初始化的时候都是隐藏的 self.__largeRadarField.hide() self.__thinRadarField.hide() self.__roundRadarField.hide() # --------------------------雷达初始化(上----------------------------- # 设置坦克颜色:RGB self.setColor(0, 200, 100) self.setGunColor(0, 200, 100) self.setRadarColor(0, 200, 100) self.setBulletsColor(0, 200, 100) # 设置原点: # 雷达范围 self.__radarField.setTransformOriginPoint(x, y) self.__largeRadarField.setTransformOriginPoint(x, y) self.__thinRadarField.setTransformOriginPoint(x, y) # 车身 x = self.__baseWidth / 2 y = self.__baseHeight / 2 self.__base.setTransformOriginPoint(x, y) # 炮膛 x = self.__gunWidth / 2 y = self.__gunHeight / 2 self.__gun.setTransformOriginPoint(x, y) # 雷达(雷达与雷达范围不是一个东西 x = self.__radarWidth / 2 y = self.__radarHeight / 2 self.__radar.setTransformOriginPoint(x, y) # 添加self.item到items中以防冲突 self.__items = set([ self, self.__base, self.__gun, self.__radar, self.__radarField, self.__largeRadarField, self.__thinRadarField, self.__roundRadarField ]) # 初始化基类(初始化子类中的初始化函数 self.init() # 当前动画 self.__currentAnimation = [] # self.a = time.time() ''' 重写QGraphicsItem类的advance函数 ''' def advance(self, i): """ if i ==1: print(time.time() - self.a) self.a = time.time() """ # 如果机器人的血量小于等于0 if self.__health <= 0: self.gamePlace = len(self.__parent.aliveBots) # print("Robot.advance:"+str(self.gamePlace)) self.__parent.placeList.append(self.__repr__()) self.__death() # 如果当前的动画列表为空 if self.__currentAnimation == []: try: self.__currentAnimation = self.__physics.animation.list.pop() except IndexError: if self.__physics.animation.name == "target": try: self.__physics.animation = self.__runAnimation self.__currentAnimation = self.__physics.animation.list.pop( ) except IndexError: pass else: self.stop() try: self.run() except: traceback.print_exc() exit(-1) self.__physics.reverse() try: self.__currentAnimation = self.__physics.animation.list.pop( ) except: pass if i == 1: try: command = self.__currentAnimation.pop() # load animation # 读取并执行移动命令 dx, dy = self.__getTranslation(command["move"]) self.setPos(dx, dy) # 读取并执行转向命令 angle = self.__getRotation(command["turn"]) self.__base.setRotation(angle) if self.__gunLock.lower() == 'base': self.__gun.setRotation(angle) if self.__radarLock.lower() == 'base': self.__setRadarRotation(angle) # gun Rotation angle = self.__getGunRotation(command["gunTurn"]) self.__gun.setRotation(angle) if self.__radarLock.lower() == 'gun': self.__setRadarRotation(angle) # radar Rotation angle = self.__getRadarRotation(command["radarTurn"]) self.__setRadarRotation(angle) # asynchronous fire #读取并执行开火命令 if command["fire"] != 0: self.makeBullet(command["fire"]) except: pass else: # 执行子类的函数——————————————————————!!!!!!!!! self.sensors() # collisions for item in set(self.__base.collidingItems(1)) - self.__items: if isinstance(item, QGraphicsRectItem): # wall Collision self.__wallRebound(item) elif isinstance(item, Robot): if item.__base.collidesWithItem(self.__base): # robot Collision self.__robotRebound(item) elif isinstance(item, Bullet): # bullet colision self.__bulletRebound(item) elif isinstance(item, radarField): if item.robot.__physics.animation.name != "target": # targetSpotted self.__targetSeen(item) # -------------------------------------------炮膛------------------------------------------- # 炮膛转向 def gunTurn(self, angle): s = 1 if angle < 0: s = -1 steps = int(s * angle / self.__physics.step) a = angle % self.__physics.step if a != 0: self.__physics.gunTurn.append(s * a) for i in range(steps): self.__physics.gunTurn.append(s * self.__physics.step) def lockGun(self, part): self.__gunLock = part # 设置炮膛颜色 def setGunColor(self, r, g, b): color = QColor(r, g, b) mask = self.__gun.pixmap.createMaskFromColor(self.gunMaskColor, 1) p = QPainter(self.__gun.pixmap) p.setPen(QColor(r, g, b)) p.drawPixmap(self.__gun.pixmap.rect(), mask, mask.rect()) p.end() self.__gun.setPixmap(self.__gun.pixmap) self.gunMaskColor = QColor(r, g, b) # -------------------------------------------车身(下------------------------------------- # 车身的移动方式 def move(self, distance): s = 1 if distance < 0: s = -1 steps = int(s * distance / self.__physics.step) d = distance % self.__physics.step if d != 0: self.__physics.move.append(s * d) for i in range(steps): self.__physics.move.append(s * self.__physics.step) # 转向方法,传入的参数为一个角度(类型未知 def turn(self, angle): s = 1 if angle < 0: s = -1 steps = int(s * angle / self.__physics.step) a = angle % self.__physics.step if a != 0: self.__physics.turn.append(s * a) for i in range(steps): self.__physics.turn.append(s * self.__physics.step) # 设置车身颜色 def setColor(self, r, g, b): color = QColor(r, g, b) mask = self.__base.pixmap.createMaskFromColor(self.maskColor, 1) p = QPainter(self.__base.pixmap) p.setPen(QColor(r, g, b)) p.drawPixmap(self.__base.pixmap.rect(), mask, mask.rect()) p.end() self.__base.setPixmap(self.__base.pixmap) self.maskColor = QColor(r, g, b) # ---------------------------------------------雷达(下----------------------------- def setRadarField(self, form): ''' 设置《雷达范围》类型 类型:normal、large、thin、round 根据传入的类型来设置显示的类型(其他类型的就hide ''' if form.lower() == "normal": self.__radarField.show() self.__largeRadarField.hide() self.__thinRadarField.hide() self.__roundRadarField.hide() if form.lower() == "large": self.__radarField.hide() self.__largeRadarField.show() self.__thinRadarField.hide() self.__roundRadarField.hide() if form.lower() == "thin": self.__radarField.hide() self.__largeRadarField.hide() self.__thinRadarField.show() self.__roundRadarField.hide() if form.lower() == "round": self.__radarField.hide() self.__largeRadarField.hide() self.__thinRadarField.hide() self.__roundRadarField.show() def lockRadar(self, part): self.__radarLock = part # 雷达转向 def radarTurn(self, angle): s = 1 if angle < 0: s = -1 steps = int(s * angle / self.__physics.step) a = angle % self.__physics.step if a != 0: self.__physics.radarTurn.append(s * a) for i in range(steps): self.__physics.radarTurn.append(s * self.__physics.step) # 设置雷达颜色(雷达罩的颜色,在坦克车身上的 def setRadarColor(self, r, g, b): color = QColor(r, g, b) mask = self.__radar.pixmap.createMaskFromColor(self.radarMaskColor, 1) p = QPainter(self.__radar.pixmap) p.setPen(QColor(r, g, b)) p.drawPixmap(self.__radar.pixmap.rect(), mask, mask.rect()) p.end() self.__radar.setPixmap(self.__radar.pixmap) self.radarMaskColor = QColor(r, g, b) # 设置雷达是否可见,将几个雷达类型都设置为统一的 def radarVisible(self, bol): self.__radarField.setVisible(bol) self.__roundRadarField.setVisible(bol) self.__thinRadarField.setVisible(bol) self.__largeRadarField.setVisible(bol) # --------------------------------------------子弹(下------------------------------------ def fire(self, power): # asynchronous fire self.stop() bullet = Bullet(power, self.bulletColor, self) self.__physics.fire.append(bullet) self.__items.add(bullet) self.__parent.addItem(bullet) bullet.hide() return id(bullet) # 制造子弹,传入的参数是个子弹对象 def makeBullet(self, bullet): bullet.show() pos = self.pos() angle = self.__gun.rotation() # to find the initial position x = pos.x() + self.__baseWidth / 2.0 y = pos.y() + self.__baseHeight / 2.0 dx = -math.sin(math.radians(angle)) * self.__gunWidth / 2.0 dy = math.cos(math.radians(angle)) * self.__gunHeight / 2.0 pos.setX(x + dx) pos.setY(y + dy) bot = self #设置一些子弹的属性 bullet.init(pos, angle, self.__parent) #生成子弹后,会扣除自己的血量,(如果击中敌人会回血 self.__changeHealth(self, -bullet.power) return id(bullet) # 设置子弹颜色 def setBulletsColor(self, r, g, b): self.bulletColor = QColor(r, g, b) # ---------------------------------------整体的方法(下-------------------------------------- #停止方法,调用后会使__physics产生一个新的动画 def stop(self): self.__physics.newAnimation() # 获取地图尺寸 def getMapSize(self): return self.__mapSize # 获取位置 def getPosition(self): p = self.pos() r = self.__base.boundingRect() return QPointF(p.x() + r.width() / 2, p.y() + r.height() / 2) # 获取炮膛朝向 def getGunHeading(self): angle = self.__gun.rotation() # if angle > 360: # a = int(angle) / 360 # angle = angle - (360*a) return angle # 获取朝向 def getHeading(self): return self.__base.rotation() # 获取雷达朝向 def getRadarHeading(self): return self.__radar.rotation() def reset(self): self.__physics.reset() self.__currentAnimation = [] # 获取敌人 def getEnemiesLeft(self): l = [] for bot in self.__parent.aliveBots: dic = {"id": id(bot), "name": bot.__repr__()} l.append(dic) return l # 向细节面板加内容的方法 def rPrint(self, msg): self.info.out.add(str(msg)) def pause(self, duration): self.stop() for i in range(int(duration)): self.__physics.move.append(0) self.stop() # ---------------------------------下面为私有方法------------------------------------------ def __getTranslation(self, step): angle = self.__base.rotation() pos = self.pos() x = pos.x() y = pos.y() dx = -math.sin(math.radians(angle)) * step dy = math.cos(math.radians(angle)) * step # print(dx, dy) return x + dx, y + dy def __setRadarRotation(self, angle): self.__radar.setRotation(angle) self.__radarField.setRotation(angle) self.__largeRadarField.setRotation(angle) self.__thinRadarField.setRotation(angle) def __getRotation(self, alpha): return self.__base.rotation() + alpha def __getGunRotation(self, alpha): return self.__gun.rotation() + alpha def __getRadarRotation(self, alpha): return self.__radar.rotation() + alpha def __wallRebound(self, item): self.reset() if item.name == 'left': x = self.__physics.step * 1.1 y = 0 elif item.name == 'right': x = -self.__physics.step * 1.1 y = 0 elif item.name == 'top': x = 0 y = self.__physics.step * 1.1 elif item.name == 'bottom': x = 0 y = -self.__physics.step * 1.1 self.setPos(self.pos().x() + x, self.pos().y() + y) self.__changeHealth(self, -1) self.stop() try: # 执行子类的方法-----------!!!!!!!!!!!!!!! self.onHitWall() except: traceback.print_exc() exit(-1) animation = self.__physics.makeAnimation() if animation != []: self.__currentAnimation = animation def __robotRebound(self, robot): try: self.reset() robot.reset() angle = self.__base.rotation() pos = self.pos() x = pos.x() y = pos.y() dx = -math.sin(math.radians(angle)) * self.__physics.step * 1.1 dy = math.cos(math.radians(angle)) * self.__physics.step * 1.1 self.setPos(x - dx, y - dy) pos = robot.pos() x = pos.x() y = pos.y() robot.setPos(x + dx, y + dy) self.__changeHealth(robot, -1) self.__changeHealth(self, -1) self.stop() # 执行子类的函数 self.onRobotHit(id(robot), robot.__repr__()) animation = self.__physics.makeAnimation() if animation != []: self.__currentAnimation = animation robot.stop() robot.onHitByRobot(id(self), self.__repr__()) animation = robot.__physics.makeAnimation() if animation != []: robot.__currentAnimation = animation except: traceback.print_exc() exit(-1) def __bulletRebound(self, bullet): self.__changeHealth(self, -3 * bullet.power) try: if bullet.robot in self.__parent.aliveBots: self.__changeHealth(bullet.robot, 2 * bullet.power) self.stop() self.onHitByBullet(id(bullet.robot), bullet.robot.__repr__(), bullet.power) animation = self.__physics.makeAnimation() if animation != []: self.__currentAnimation = animation bullet.robot.stop() bullet.robot.onBulletHit(id(self), id(bullet)) animation = bullet.robot.__physics.makeAnimation() if animation != []: bullet.robot.__currentAnimation = animation self.__parent.removeItem(bullet) except: pass def __targetSeen(self, target): self.stop() anim = target.robot.__currentAnimation target.robot.__physics.animation = target.robot.__targetAnimation target.robot.__physics.reset() try: target.robot.onTargetSpotted(id(self), self.__repr__(), self.getPosition()) except: traceback.print_exc() exit(-1) target.robot.__physics.newAnimation() target.robot.__physics.reverse() try: target.robot.__currentAnimation = target.robot.__physics.animation.list.pop( ) except: target.robot.__physics.animation = target.robot.__runAnimation target.robot.__currentAnimation = anim def __changeHealth(self, bot, value): if bot.__health + value >= 100: bot.__health = 100 else: bot.__health = bot.__health + value try: bot.progressBar.setValue(bot.__health) except: pass def removeMyProtectedItem(self, item): self.__items.remove(item) def __death(self): try: self.icon.setIcon(QIcon(os.getcwd() + "/robotImages/dead.png")) self.icon2.setIcon(QIcon(os.getcwd() + "/robotImages/dead.png")) self.progressBar.setValue(0) except: pass self.__parent.deadBots.append(self) self.__parent.aliveBots.remove(self) try: self.onRobotDeath() except: traceback.print_exc() exit(-1) self.__parent.removeItem(self) if len(self.__parent.aliveBots) <= 1: # 如果生存的机器人小于等于1,则结束战斗 self.__parent.battleFinished() def __repr__(self): repr = self.__repr.split(".") return repr[1].replace("'>", "")
class SpecifyTarget(Plugin): def __init__(self, parent=None): super(SpecifyTarget, self).__init__() self.setObjectName('specify_target') # should be plugin name # ~ self.needsGraphicsView = True self.needsEventFilter = True self.fname = '' self.image = None self.targetImg = None # declare all parameter widgets below vbox1 = QVBoxLayout() hbox1 = QHBoxLayout() self.dblNumTargets = QDoubleSpinBox(objectName='dblNumTargets') self.dblNumTargets.setMinimum(1) self.dblNumTargets.setMaximum(5) self.dblNumTargets.setValue(1) self.dblNumTargets.setSingleStep(1) self.chkSaveTarget = QCheckBox('save target.png', objectName='chkSaveTarget') hbox1.addWidget(self.dblNumTargets) hbox1.addWidget(QLabel('num targets')) hbox1.addWidget(self.chkSaveTarget) labels = ['Rectangle', 'Square', 'Ellipse', 'Circle'] groupBox1, self.bgrpShape = radio_filler('target shape', labels, 2) labels = ['opposite\ncorners', 'center\n-edge'] groupBox2, self.bgrpDrawMethod = radio_filler('draw method', labels, 2) self.lblStatus = QLabel(wordWrap=True) groupBox2.layout().addWidget(self.lblStatus, 0, 2) self.bgrpShape.button(0).setChecked(True) self.bgrpDrawMethod.button(0).setChecked(True) self.bgrpShape.buttonClicked.connect(self.radio_handler) self.targetView = QGraphicsView() scene = QGraphicsScene() self.targetView.setScene(scene) # ~ vbox1.addLayout(hbox1) # ~ vbox1.addWidget(groupBox1) vbox1.addWidget(self.targetView) vbox1.addWidget(groupBox2) vbox1.setStretch(0, 3) vbox1.setStretch(1, 1) self.setLayout(vbox1) self.chkSaveTarget.setChecked(True) # class variables self.targetShape = None self.target = None self.begin = None self.end = None self.training = False def getParams(self): params = super(SpecifyTarget, self).getParams() params['fname'] = self.fname self.Params = params return params def setParams(self, params): params = super(SpecifyTarget, self).setParams(params) self.fname = self.Params['fname'] def showImg(self, img): if len(img.shape) == 3: # ~ #img = cv.cvtColor(img, cv.COLOR_BGR2RGB) height, width, bpc = img.shape bpl = bpc * width # ~ print(subimage.data, ', ', width, ', ', height, ', ', bpl) qimg = QImage(img, width, height, bpl, QImage.Format_RGB888) else: height, width = img.shape bpc = 1 bpl = bpc * width qimg = QImage(img.data, width, height, bpl, QImage.Format_Grayscale8) self.image = QGraphicsPixmapItem(QPixmap.fromImage(qimg)) self.targetImg = img self.targetView.scene().clear() self.targetView.scene().addItem(self.image) self.targetView.fitInView(self.image.boundingRect(), Qt.KeepAspectRatio) def eventFilter(self, source, event): if event.type() == QtCore.QEvent.MouseButtonPress: print(time.time(), ' mousebuttonpress') if isinstance(source, QGraphicsView): self.mouseButtonPress(source) return True elif isinstance( source, QGraphicsView) or source.objectName() == 'MainWindowWindow': if self.training: if (event.type() == QtCore.QEvent.MouseMove and source.objectName() == 'MainWindowWindow'): if event.buttons() != QtCore.Qt.NoButton: # ~ global end self.mouseMove() return True elif (event.type() == QtCore.QEvent.MouseButtonRelease and source.objectName() == 'MainWindowWindow'): self.mouseButtonRelease() return True # this is for eventFilter. when True, stop processing events return False # this is for eventFilter. when False, keep processing events def mouseButtonPress(self, source): pos = QtGui.QCursor.pos() viewPoint = source.mapFromGlobal(pos) scenePoint = source.mapToScene(viewPoint) # ~ global graphicsView self.graphicsView = source self.begin = scenePoint self.training = True # ~ print(pos) # ~ print(self.begin) rect = QRectF(self.begin, self.begin) # ~ print(rect) # ~ self.targetShape = QGraphicsRectItem(rect) # ~ global targetShapes if self.targetShape is not None: try: print('targetShape ', self.targetShape) source.scene().removeItem(self.targetShape) except RuntimeError: pass # ~ bshape = self.bgrpShape.checkedId() bshape = 0 # ~ print('bshape ', bshape) if bshape == 0: # Rectangle self.targetShape = QGraphicsRectItem(rect) elif bshape == 1: # Square self.targetShape = QGraphicsRectItem(rect) elif bshape == 2: # Ellipse self.targetShape = QGraphicsEllipseItem(rect) elif bshape == 3: # Circle self.targetShape = QGraphicsEllipseItem(rect) self.targetShape.setZValue(1) pen = QPen(QColor(0, 255, 0), 5) self.targetShape.setPen(pen) try: source.scene().addItem(self.targetShape) except RuntimeError: pass print('mousie pressie') def mouseMove(self): pos = QtGui.QCursor.pos() viewPoint = self.graphicsView.mapFromGlobal(pos) scenePoint = self.graphicsView.mapToScene(viewPoint) self.end = scenePoint # ~ print('end :', self.end) try: # ~ bshape = self.bgrpShape.checkedId() bshape = 0 bmethod = self.bgrpDrawMethod.checkedId() if bshape == 3: # circle if bmethod == 1: # center radius circle r = QLineF(self.begin, self.end).length() cx = self.begin.x() cy = self.begin.y() # ~ x1 = self.begin.x() - r # ~ y1 = self.begin.y() - r # ~ x2 = self.begin.x() + r # ~ y2 = self.begin.y() + r elif bmethod == 0: r = QLineF(self.begin, self.end).length() / 2 # ~ center = line.center() # not available until qt > 5.8 cx = float(self.begin.x() + self.end.x()) / 2.0 cy = float(self.begin.y() + self.end.y()) / 2.0 x1 = cx - r y1 = cy - r x2 = cx + r y2 = cy + r # ~ print('begin ', self.begin, ' end ', self.end) # ~ print('r ', r, ' cx ', cx, ' cy ', cy) elif bshape == 1: # square if bmethod == 1: # center radius circle r = QLineF(self.begin, self.end).length() / np.sqrt(2) cx = self.begin.x() cy = self.begin.y() x1 = cx - r y1 = cy - r x2 = cx + r y2 = cy + r elif bmethod == 0: line = QLineF(self.begin, self.end) r = line.length() / np.sqrt(2) sx = np.sign(line.dx()) sy = np.sign(line.dy()) x1 = self.begin.x() y1 = self.begin.y() x2 = self.begin.x() + sx * r y2 = self.begin.y() + sy * r #rectangles and ellipses else: if bmethod == 0: # 2 corners rectangle x1 = self.begin.x() y1 = self.begin.y() x2 = self.end.x() y2 = self.end.y() elif bmethod == 1: # center/corner rectangle x1 = self.begin.x() - abs(self.begin.x() - self.end.x()) y1 = self.begin.y() - abs(self.begin.y() - self.end.y()) x2 = self.begin.x() + abs(self.begin.x() - self.end.x()) y2 = self.begin.y() + abs(self.begin.y() - self.end.y()) # ~ self.targetShape.setRect(QRectF(begin, end)) # ~ idx = len(self.targetShapes)-1 self.targetShape.setRect(QRectF(QPointF(x1, y1), QPointF(x2, y2))) self.lblStatus.setText('drawing target') # ~ print('x1, y1, x2, y2: ', x1, y1, x2, y2) except RuntimeError as e: # ~ pass print('RuntimeError in mouseMove. ', e) def mouseButtonRelease(self): self.training = False # time to save target image. but first we need to # check the sizes self.graphicsItems.append(self.targetShape) # only save to target.png if checkbox is checked # ~ if self.chkSaveTarget.isChecked(): try: rect = self.targetShape.rect() print('target rect ', rect) x1 = int(round(min([rect.left(), rect.right()]))) y1 = int(round(min([rect.top(), rect.bottom()]))) x2 = int(round(max([rect.left(), rect.right()]))) y2 = int(round(max([rect.top(), rect.bottom()]))) # ~ print( 'x1, x2, y1, y2: %d, %d, %d, %d' % (x1, x2, y1, y2)) # ~ print('width: ', rect.width(), '. height: ', rect.height()) # for circle and ellipse, we want width and height to be divisible # by two. so we'll add an extra pixel if necessary x2 += (x2 - x1) % 2 # remainder will be 1 for odd widths y2 += (y2 - y1) % 2 # ~ print( 'x2, y2: %d, %d' % (x2, y2)) if abs(y2 - y1) > 20: # ~ dx = x2-x1 # ~ dy = y2-y1 # use +1 because slice notation is screwy subimage = self.inputImg[y1:y2, x1:x2].copy() self.showImg(subimage) if subimage.ndim == 3: subimage = cv.cvtColor(subimage, cv.COLOR_RGB2BGR) fname = 'target_' + str(uuid4()) + '.png' # ~ print(fname) self.fname = fname cv.imwrite(fname, subimage) self.lblStatus.setText('target set') else: self.lblStatus.setText('') self.graphicsView.scene().removeItem(self.targetShape) except RuntimeError as e: print('RuntimeError in mouseButtonRelease. ', e) def mainFunc(self, playing, scriptList, row): # ~ print(self.inputImg) if self.inputImg is not None: self.outputImg = self.inputImg # here we don't even need to # make a copy. it's a straight # pass through. # ~ self.outputImg = self.inputImg.copy() # ~ print('copy') if self.image is None: if self.fname: img = cv.imread(self.fname) if img is None: self.lblStatus.setText('target file not found') else: if img.ndim == 3: img = cv.cvtColor(img, cv.COLOR_BGR2RGB) self.showImg(img) def resizeEvent(self, event=0): try: rect = self.targetView.scene().items()[0].boundingRect() self.targetView.fitInView(rect, Qt.KeepAspectRatio) except IndexError: pass def radio_handler(self): self.bgrpDrawMethod.button(1).setChecked(True)
class NetScene(QGraphicsScene): SPACE_WIDTH = 200 # 留白宽度 def __init__(self, parent, announces, api): super().__init__(parent) self.announces = announces self.api = api api['Scene.setBackgroundColor'] = self.setBackgroundBrush api['Scene.setBackgroundPixmap'] = self.setBackgroundPixmap api['Scene.setLayout'] = self.setLayout api['Scene.renderNode'] = self.renderNode api['Scene.renderEdge'] = self.renderEdge api['Scene.selectedNode'] = lambda: self.selected_node announces['addICNNode'].append(self.addICNNode) announces['addICNEdge'].append(self.addICNEdge) self.node_table = {} # { node_id:node_item, ... } self.edge_table = {} # { edge_id:edge_item, ... } self.cache_pixmap = None self.background_item = QGraphicsPixmapItem() self.addItem(self.background_item) self.selected_node = None def setLayout(self, layout): """ 设置场景中节点的位置 :param layout: {node_id:(x,y), ...} :return: None """ for node_id, (x, y) in layout.items(): self.node_table[node_id].checkPos(x, y) self.adjustBounding() def addICNNode(self, node_id): node_item = NodeItem(node_id) node_item.press_callback = self.pressNode node_item.release_callback = self.releaseNode node_item.double_click_callback = self.announces['doubleClickNode'] node_item.move_callback = self.moveNode self.node_table[node_id] = node_item self.addItem(node_item) self.update() def addICNEdge(self, edge_id): edge_item = EdgeItem(edge_id) edge_item.double_click_callback = self.announces['doubleClickEdge'] src_id, dst_id = edge_id src_item = self.node_table[src_id] dst_item = self.node_table[dst_id] edge_item.adjust(src_item.pos(), dst_item.pos()) self.edge_table[edge_id] = edge_item self.addItem(edge_item) self.update() def renderNode(self, node_id, style: dict): self.node_table[node_id].setStyle(style) def renderEdge(self, edge_id, style: dict): self.edge_table[edge_id].setStyle(style) # ------------------------------------------------------------------------------------------------------------------ def setBackgroundPixmap(self, pixmap): if pixmap is None: self.background_item.hide() else: if self.cache_pixmap is not pixmap: self.cache_pixmap = pixmap self.background_item.setPixmap(pixmap) # 设置位置居中 rect = self.background_item.boundingRect() self.background_item.setPos(-rect.center()) self.background_item.show() def adjustBounding(self): self.setSceneRect( self.itemsBoundingRect().adjusted( -self.SPACE_WIDTH, -self.SPACE_WIDTH, self.SPACE_WIDTH, self.SPACE_WIDTH ) ) self.update() # ------------------------------------------------------------------------------------------------------------------ def pressNode(self, node_id): self.selected_node= node_id self.announces['selectedNode'](self.selected_node) def moveNode(self, src_id): """ 当一个节点位置变化时,调整与该节点链接的边 :param src_id: 节点id :return: None """ graph = self.api['Sim.graph']() # 邻接表{node_id:{neighbor_id, ...}, } src_pos = self.node_table[src_id].pos() for dst_id in graph[src_id]: dst_pos = self.node_table[dst_id].pos() self.edge_table[(src_id, dst_id)].adjust(src_pos, dst_pos) self.edge_table[(dst_id, src_id)].adjust(dst_pos, src_pos) def releaseNode(self, node_id): node_item = self.node_table[node_id] pos = node_item.pos() pos = pos.x(), pos.y() self.announces['sceneNodeMoved'](node_id, pos)
class CameraView(QGraphicsObject): font: QFont = QFont("monospace", 16) stick_link_requested = pyqtSignal(StickWidget) stick_context_menu = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject') stick_widgets_out_of_sync = pyqtSignal('PyQt_PyObject') visibility_toggled = pyqtSignal() synchronize_clicked = pyqtSignal('PyQt_PyObject') previous_photo_clicked = pyqtSignal('PyQt_PyObject') next_photo_clicked = pyqtSignal('PyQt_PyObject') sync_confirm_clicked = pyqtSignal('PyQt_PyObject') sync_cancel_clicked = pyqtSignal('PyQt_PyObject') first_photo_clicked = pyqtSignal('PyQt_PyObject') enter_pressed = pyqtSignal() def __init__(self, scale: float, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.current_highlight_color = QColor(0, 0, 0, 0) self.current_timer = -1 self.scaling = scale self.pixmap = QGraphicsPixmapItem(self) self.stick_widgets: List[StickWidget] = [] self.link_cam_text = QGraphicsSimpleTextItem("Link camera...", self) self.link_cam_text.setZValue(40) self.link_cam_text.setVisible(False) self.link_cam_text.setFont(CameraView.font) self.link_cam_text.setPos(0, 0) self.link_cam_text.setPen(QPen(QColor(255, 255, 255, 255))) self.link_cam_text.setBrush(QBrush(QColor(255, 255, 255, 255))) self.show_add_buttons = False self.camera = None self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.show_stick_widgets = False self.setAcceptHoverEvents(True) self.stick_edit_mode = False self.original_pixmap = self.pixmap.pixmap() self.hovered = False self.mode = 0 # TODO make enum Mode self.click_handler = None self.double_click_handler: Callable[[int, int], None] = None self.stick_widget_mode = StickMode.Display self.highlight_animation = QPropertyAnimation(self, b"highlight_color") self.highlight_animation.setEasingCurve(QEasingCurve.Linear) self.highlight_animation.valueChanged.connect( self.handle_highlight_color_changed) self.highlight_rect = QGraphicsRectItem(self) self.highlight_rect.setZValue(4) self.highlight_rect.setPen(QPen(QColor(0, 0, 0, 0))) self.title_btn = Button('btn_title', '', parent=self) self.title_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, False) self.title_btn.setZValue(5) self.title_btn.setVisible(False) self.sticks_without_width: List[Stick] = [] self.current_image_name: str = '' self.control_widget = ControlWidget(parent=self) self.control_widget.setFlag(QGraphicsItem.ItemIgnoresTransformations, False) self.control_widget.setVisible(True) self._connect_control_buttons() self.image_available = True self.blur_eff = QGraphicsBlurEffect() self.blur_eff.setBlurRadius(5.0) self.blur_eff.setEnabled(False) self.pixmap.setGraphicsEffect(self.blur_eff) self.overlay_message = QGraphicsSimpleTextItem('not available', parent=self) font = self.title_btn.font font.setPointSize(48) self.overlay_message.setFont(font) self.overlay_message.setBrush(QBrush(QColor(200, 200, 200, 200))) self.overlay_message.setPen(QPen(QColor(0, 0, 0, 200), 2.0)) self.overlay_message.setVisible(False) self.overlay_message.setZValue(6) self.stick_box = QGraphicsRectItem(parent=self) self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True) self.stick_box.setVisible(False) self.stick_box_start_pos = QPoint() def _connect_control_buttons(self): self.control_widget.synchronize_btn.clicked.connect( lambda: self.synchronize_clicked.emit(self)) self.control_widget.prev_photo_btn.clicked.connect( lambda: self.previous_photo_clicked.emit(self)) self.control_widget.next_photo_btn.clicked.connect( lambda: self.next_photo_clicked.emit(self)) self.control_widget.accept_btn.clicked.connect( lambda: self.sync_confirm_clicked.emit(self)) self.control_widget.cancel_btn.clicked.connect( lambda: self.sync_cancel_clicked.emit(self)) self.control_widget.first_photo_btn.clicked.connect( lambda: self.first_photo_clicked.emit(self)) def paint(self, painter: QPainter, option: PyQt5.QtWidgets.QStyleOptionGraphicsItem, widget: QWidget): if self.pixmap.pixmap().isNull(): return painter.setRenderHint(QPainter.Antialiasing, True) if self.show_stick_widgets: brush = QBrush(QColor(255, 255, 255, 100)) painter.fillRect(self.boundingRect(), brush) if self.mode and self.hovered: pen = QPen(QColor(0, 125, 200, 255)) pen.setWidth(4) painter.setPen(pen) def boundingRect(self) -> PyQt5.QtCore.QRectF: return self.pixmap.boundingRect().united( self.title_btn.boundingRect().translated(self.title_btn.pos())) def initialise_with(self, camera: Camera): if self.camera is not None: self.camera.stick_added.disconnect(self.handle_stick_created) self.camera.sticks_added.disconnect(self.handle_sticks_added) self.camera.stick_removed.disconnect(self.handle_stick_removed) self.camera.sticks_removed.disconnect(self.handle_sticks_removed) self.camera.stick_changed.disconnect(self.handle_stick_changed) self.camera = camera self.prepareGeometryChange() self.set_image(camera.rep_image, Path(camera.rep_image_path).name) self.title_btn.set_label(self.camera.folder.name) self.title_btn.set_height(46) self.title_btn.fit_to_contents() self.title_btn.set_width(int(self.boundingRect().width())) self.title_btn.setPos(0, self.boundingRect().height()) self.control_widget.title_btn.set_label(self.camera.folder.name) self.camera.stick_added.connect(self.handle_stick_created) self.camera.sticks_added.connect(self.handle_sticks_added) self.camera.stick_removed.connect(self.handle_stick_removed) self.camera.sticks_removed.connect(self.handle_sticks_removed) self.camera.stick_changed.connect(self.handle_stick_changed) self.control_widget.set_font_height(32) self.control_widget.set_widget_height( self.title_btn.boundingRect().height()) self.control_widget.set_widget_width(int(self.boundingRect().width())) self.control_widget.setPos(0, self.pixmap.boundingRect().height() ) #self.boundingRect().height()) self.control_widget.set_mode('view') self.update_stick_widgets() def set_image(self, img: Optional[np.ndarray] = None, image_name: Optional[str] = None): if img is None: self.show_overlay_message('not available') return self.show_overlay_message(None) self.prepareGeometryChange() barray = QByteArray(img.tobytes()) image = QImage(barray, img.shape[1], img.shape[0], QImage.Format_BGR888) self.original_pixmap = QPixmap.fromImage(image) self.pixmap.setPixmap(self.original_pixmap) self.highlight_rect.setRect(self.boundingRect()) self.current_image_name = image_name def update_stick_widgets(self): stick_length = 60 for stick in self.camera.sticks: sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) stick_length = stick.length_cm self.update_stick_box() self.scene().update() def scale_item(self, factor: float): self.prepareGeometryChange() pixmap = self.original_pixmap.scaledToHeight( int(self.original_pixmap.height() * factor)) self.pixmap.setPixmap(pixmap) self.__update_title() def set_show_stick_widgets(self, value: bool): for sw in self.stick_widgets: sw.setVisible(value) self.scene().update() def hoverEnterEvent(self, e: QGraphicsSceneHoverEvent): self.hovered = True self.scene().update(self.sceneBoundingRect()) def hoverLeaveEvent(self, e: QGraphicsSceneHoverEvent): self.hovered = False self.scene().update(self.sceneBoundingRect()) def mousePressEvent(self, e: QGraphicsSceneMouseEvent): super().mousePressEvent(e) def mouseReleaseEvent(self, e: QGraphicsSceneMouseEvent): if self.mode == 1: self.click_handler(self.camera) def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent): if self.stick_widget_mode == StickMode.EditDelete: x = event.pos().toPoint().x() y = event.pos().toPoint().y() stick = self.camera.create_new_sticks( [(np.array([[x, y - 50], [x, y + 50]]), 3)], self.current_image_name)[ 0] #self.dataset.create_new_stick(self.camera) self.sticks_without_width.append(stick) def set_button_mode(self, click_handler: Callable[[Camera], None], data: str): self.mode = 1 # TODO make a proper ENUM self.click_handler = lambda c: click_handler(c, data) def set_display_mode(self): self.mode = 0 # TODO make a proper ENUM self.click_handler = None def _remove_stick_widgets(self): for sw in self.stick_widgets: sw.setParentItem(None) self.scene().removeItem(sw) sw.deleteLater() self.stick_widgets.clear() def handle_stick_created(self, stick: Stick): if stick.camera_id != self.camera.id: return sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) self.stick_widgets_out_of_sync.emit(self) self.update() def handle_stick_removed(self, stick: Stick): if stick.camera_id != self.camera.id: return stick_widget = next( filter(lambda sw: sw.stick.id == stick.id, self.stick_widgets)) self.disconnect_stick_widget_signals(stick_widget) self.stick_widgets.remove(stick_widget) stick_widget.setParentItem(None) self.scene().removeItem(stick_widget) stick_widget.deleteLater() self.update() def handle_sticks_removed(self, sticks: List[Stick]): if sticks[0].camera_id != self.camera.id: return for stick in sticks: to_remove: StickWidget = None for sw in self.stick_widgets: if sw.stick.id == stick.id: to_remove = sw break self.stick_widgets.remove(to_remove) to_remove.setParentItem(None) if self.scene() is not None: self.scene().removeItem(to_remove) to_remove.deleteLater() self.update() def handle_sticks_added(self, sticks: List[Stick]): if len(sticks) == 0: return if sticks[0].camera_id != self.camera.id: return for stick in sticks: sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) self.update_stick_box() self.stick_widgets_out_of_sync.emit(self) self.update() def connect_stick_widget_signals(self, stick_widget: StickWidget): stick_widget.delete_clicked.connect( self.handle_stick_widget_delete_clicked) stick_widget.stick_changed.connect(self.handle_stick_widget_changed) stick_widget.link_initiated.connect(self.handle_stick_link_initiated) stick_widget.right_clicked.connect( self.handle_stick_widget_context_menu) def disconnect_stick_widget_signals(self, stick_widget: StickWidget): stick_widget.delete_clicked.disconnect( self.handle_stick_widget_delete_clicked) stick_widget.stick_changed.disconnect(self.handle_stick_widget_changed) stick_widget.link_initiated.disconnect( self.handle_stick_link_initiated) stick_widget.right_clicked.disconnect( self.handle_stick_widget_context_menu) def handle_stick_widget_delete_clicked(self, stick: Stick): self.camera.remove_stick(stick) def set_stick_widgets_mode(self, mode: StickMode): self.stick_widget_mode = mode for sw in self.stick_widgets: sw.set_mode(mode) self.set_stick_edit_mode(mode == StickMode.Edit) def handle_stick_widget_changed(self, stick_widget: StickWidget): self.camera.stick_changed.emit(stick_widget.stick) def handle_stick_changed(self, stick: Stick): if stick.camera_id != self.camera.id: return sw = next( filter(lambda _sw: _sw.stick.id == stick.id, self.stick_widgets)) sw.adjust_line() sw.update_tooltip() def handle_stick_link_initiated(self, stick_widget: StickWidget): self.stick_link_requested.emit(stick_widget) def get_top_left(self) -> QPointF: return self.sceneBoundingRect().topLeft() def get_top_right(self) -> QPointF: return self.sceneBoundingRect().topRight() def highlight(self, color: Optional[QColor]): if color is None: self.highlight_animation.stop() self.highlight_rect.setVisible(False) return alpha = color.alpha() color.setAlpha(0) self.highlight_animation.setStartValue(color) self.highlight_animation.setEndValue(color) color.setAlpha(alpha) self.highlight_animation.setKeyValueAt(0.5, color) self.highlight_animation.setDuration(2000) self.highlight_animation.setLoopCount(-1) self.highlight_rect.setPen(QPen(color)) self.highlight_rect.setVisible(True) self.highlight_animation.start() @pyqtProperty(QColor) def highlight_color(self) -> QColor: return self.current_highlight_color @highlight_color.setter def highlight_color(self, color: QColor): self.current_highlight_color = color def handle_highlight_color_changed(self, color: QColor): self.highlight_rect.setBrush(QBrush(color)) self.update() def handle_stick_widget_context_menu(self, sender: Dict[str, StickWidget]): self.stick_context_menu.emit(sender['stick_widget'], self) def show_overlay_message(self, msg: Optional[str]): if msg is None: self.overlay_message.setVisible(False) self.blur_eff.setEnabled(False) return self.overlay_message.setText(msg) self.overlay_message.setPos( self.pixmap.boundingRect().center() - QPointF(0.5 * self.overlay_message.boundingRect().width(), 0.5 * self.overlay_message.boundingRect().height())) self.overlay_message.setVisible(True) self.blur_eff.setEnabled(True) def show_status_message(self, msg: Optional[str]): if msg is None: self.control_widget.set_title_text(self.camera.folder.name) else: self.control_widget.set_title_text(msg) def update_stick_box(self): left = 9000 right = 0 top = 9000 bottom = -1 for stick in self.camera.sticks: left = min(left, min(stick.top[0], stick.bottom[0])) right = max(right, max(stick.top[0], stick.bottom[0])) top = min(top, min(stick.top[1], stick.bottom[1])) bottom = max(bottom, max(stick.top[1], stick.bottom[1])) left -= 100 right += 100 top -= 100 bottom += 100 self.stick_box.setRect(left, top, right - left, bottom - top) pen = QPen(QColor(0, 100, 200, 200)) pen.setWidth(2) pen.setStyle(Qt.DashLine) self.stick_box.setPen(pen) def set_stick_edit_mode(self, is_edit: bool): if is_edit: self.update_stick_box() self.stick_box_start_pos = self.stick_box.pos() for sw in self.stick_widgets: sw.setParentItem(self.stick_box) else: offset = self.stick_box.pos() - self.stick_box_start_pos for sw in self.stick_widgets: stick = sw.stick stick.translate(np.array([int(offset.x()), int(offset.y())])) sw.setParentItem(self) sw.set_stick(stick) self.stick_box.setParentItem(None) self.stick_box = QGraphicsRectItem(self) self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True) self.stick_box.setVisible(False) self.stick_box.setVisible(is_edit) def keyPressEvent(self, event: QKeyEvent) -> None: pass def keyReleaseEvent(self, event: QKeyEvent) -> None: if event.key() in [Qt.Key_Right, Qt.Key_Tab, Qt.Key_Space]: self.control_widget.next_photo_btn.click_button(True) elif event.key() in [Qt.Key_Left]: self.control_widget.prev_photo_btn.click_button(True) elif event.key() == Qt.Key_S: self.enter_pressed.emit()
class ImageWidget(QGraphicsView): def __init__(self, *args, **kwargs): super(ImageWidget, self).__init__(*args, **kwargs) self.setObjectName('graphicsView') self.setBackgroundBrush(QColor(25, 25, 25)) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing) self.setCacheMode(self.CacheBackground) self.setViewportUpdateMode(self.SmartViewportUpdate) self._zoomDelta = 0.1 self._pixmap = None self._item = QGraphicsPixmapItem() self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable | QGraphicsPixmapItem.ItemIsMovable) self._scene = QGraphicsScene(self) self.setScene(self._scene) self._scene.addItem(self._item) def pixmap(self): return self._pixmap def setPixmap(self, pixmap, fitIn=True): self._pixmap = pixmap self._item.setPixmap(pixmap) self._item.update() self.setSceneDims() if fitIn: self.fitInView(QRectF(self._item.pos(), QSizeF( self._pixmap.size())), Qt.KeepAspectRatio) self.update() def setSceneDims(self): if not self._pixmap: return self.setSceneRect( QRectF( QPointF(0, 0), QPointF(self._pixmap.width(), self._pixmap.height())) ) def fitInView(self, rect, flags=Qt.IgnoreAspectRatio): if not self.scene() or rect.isNull(): return unity = self.transform().mapRect(QRectF(0, 0, 1, 1)) self.scale(1 / unity.width(), 1 / unity.height()) viewRect = self.viewport().rect() sceneRect = self.transform().mapRect(rect) xratio = viewRect.width() / sceneRect.width() yratio = viewRect.height() / sceneRect.height() if flags == Qt.KeepAspectRatio: xratio = yratio = min(xratio, yratio) elif flags == Qt.KeepAspectRatioByExpanding: xratio = yratio = max(xratio, yratio) self.scale(xratio, yratio) self.centerOn(rect.center()) def getPos(self, pos): spos = self.mapToScene(pos) pos = self._item.mapFromScene(spos) return pos.toPoint() if self._item.boundingRect().contains(pos) else QPoint() def wheelEvent(self, event): if event.angleDelta().y() > 0: self.zoomIn() else: self.zoomOut() def zoomIn(self): """放大""" self.zoom(1 + self._zoomDelta) def zoomOut(self): """缩小""" self.zoom(1 - self._zoomDelta) def zoom(self, scaleFactor): """ # 缩放 :param scaleFactor: 缩放的比例因子 """ factor = self.transform().scale( scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width() if factor < 0.07 or factor > 100: # 防止过大过小 return self.scale(scaleFactor, scaleFactor)
class ImageView(QGraphicsView): def __init__(self, mode): # parameter self.mode = mode # create scene self.scene = QGraphicsScene() super().__init__(self.scene) self.setScene(self.scene) # use in draw sequence # - items : Scene 에 추가된 item list # - 임시 item 들은 obj에 관리된다. self.items = [] self.obj = {} # View 출력을 위한 임시 저장장소 self._object_reset() self.rect_p0 = None self.rect_p1 = None self.select = [] # use in data load self.image = None self.pixmap = None self.image_item = None self.metadata = None # use in key event self._zoom = 0 self.KEY_SHIFT = False self.KEY_ALT = False self.KEY_CTRL = False self.KEY_MOUSE_LEFT = False # backup self.last_mask = None self.last_polygon = None # QGraphicsView setting self.setDragMode(self.mode.VIEW_DRAG_MODE) self.setMouseTracking(True) # remove scrollbar option if not self.mode.SCROLLBAR_VIEW: self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def load_image(self, image, metadata): self.reset() self.image = image # Qimage 를 가져온다. image = self.image.get_image() # pixmap data 생성 pixmap = QPixmap(image) self.pixmap = pixmap self.image_item = QGraphicsPixmapItem(pixmap) self.scene.addItem(self.image_item) self._load_metadata(metadata) def refresh(self): """ 현재 작업중인 임시 정보를 모두 삭제하고, 현재 설정값을 재설정한다. """ # Object 초기화 self._object_reset() # Mode 설정 self.setDragMode(self.mode.VIEW_DRAG_MODE) # Data Reset self.rect_p0 = None self.rect_p1 = None self.select.clear() # Key 초기화 self.KEY_SHIFT = False self.KEY_ALT = False self.KEY_MOUSE_LEFT = False # Mask Draw self._mask_draw() # update self._update() def reset(self): """ 이미지 정보를 포함하여 모든 정보를 삭제한다. """ # Scene 에 표시되는 물체들을 제거한다. self._delete_items(self.items) self.scene.removeItem(self.image_item) # backup 초기화 self.last_mask = None self.last_polygon = None # image 정보 초기화 self.image = None self.pixmap = None self.image_item = None self.metadata = None # refresh self.refresh() def restore(self): print('restore') if self.last_mask is not None and self.image is not None: self.image.restore_mask(self.last_mask) if self.last_polygon is not None: self._polygon_delete(self.last_polygon) print('1') self.last_mask = None self.last_polygon = None self._mask_draw() def callback_selected(self, e, item): """ Image Item 객체가 선택되었을 때, View 에 직접 호출 할 수 있는 함수 Image Item 객체의 event 함수에서 호출된다. """ pass def callback_polygon_double_click(self, polygon_item): if self.KEY_CTRL and self.mode.CURRENT == 0: # delete polygon # 1. remove text item if polygon_item.text_item is not None: self.items.remove(polygon_item.text_item) self.scene.removeItem(polygon_item.text_item) # 2. remove polygon self.items.remove(polygon_item) self.scene.removeItem(polygon_item) elif self.KEY_CTRL: print('2') self._polygon_delete(polygon_item) def callback_polygon_hover_enter(self, polygon_item): if self.mode.CURRENT == 0: self.setDragMode(QGraphicsView.NoDrag) def callback_polygon_hover_move(self, polygon_item): if self.mode.CURRENT == 0: self.setDragMode(QGraphicsView.NoDrag) def callback_polygon_hover_leave(self, polygon_item): if self.mode.CURRENT == 0: self.setDragMode(self.mode.VIEW_DRAG_MODE) def _load_metadata(self, metadata): """ Image Annotation 을 읽어와 View 에 출력한다. """ self.metadata = metadata polygons = metadata.out_polygons_to_list() for polygon in polygons: [attribute, x, y, id] = polygon assert len(x) == len(y), "Invalid polygon information in metadata" qpol = QPolygonF() for idx in range(len(x)): qpol.append(QPointF(x[idx], y[idx])) self._polygon_draw(qpol, attribute, id) def _update(self): pass def _delete_items(self, items): for item in items: self.scene.removeItem(item) items.clear() def _object_reset(self): """ Object 를 초기화한다. obj Dictionary 에는 view 그리기에 필요한 임시값들이 저장되어있다. """ # polygon : 현재 작성중인 polygon 좌표들을 QPointF list 형태로 가지고 있는다. if 'polygon' not in self.obj: self.obj['polygon'] = [] else: self.obj['polygon'].clear() # lines : View 에 출력되는 직선들이다. if 'lines' not in self.obj: self.obj['lines'] = [] else: self._delete_items(self.obj['lines']) self.obj['lines'].clear() # mask : magicwand mask if 'mask' in self.obj and self.obj['mask'] is not None: self.scene.removeItem(self.obj['mask']) self.obj['mask'] = None # paint sign if 'paint' in self.obj and self.obj['paint'] is not None: self.scene.removeItem(self.obj['paint']) self.obj['paint'] = None def _polygon_check(self, polygon_list): """ polygon 의 시작 위치와 종료 위치를 이용하여 생성 조건을 반환한다. """ if polygon_list is None or len(polygon_list) < 3: return False # 거리 계산 dx = polygon_list[-1].x() - polygon_list[0].x() dy = polygon_list[-1].y() - polygon_list[0].y() d = dx * dx + dy * dy if d < self.mode.POLYGON_END_THRESHOLD: # 마지막 위치 수정 del (polygon_list[-1]) polygon_list.append(polygon_list[0]) return True else: return False def _polygon_draw(self, polygon, attribute, id=-1): """ polygon 을 그린다. polygon 객체를 인수로 받아서 scene 에 추가한다. """ # Call by Value attribute = attribute.copy() pen = QPen(self.mode.DRAW_PEN_COLOR, 3) if attribute['name'] in self.mode.POLYGON_BRUSH_COLOR: brush = QBrush(self.mode.POLYGON_BRUSH_COLOR[attribute['name']]) else: brush = QBrush(self.mode.DRAW_BRUSH_COLOR) # polygon_item 생성 polygon_item = PolygonItem(self, polygon, attribute, id) polygon_item.setPen(pen) polygon_item.setBrush(brush) # scene 에 추가 self.items.append(polygon_item) self.scene.addItem(polygon_item) # polygon location self._polygon_location(polygon_item) # QGraphicPolygonItem 반환 return polygon_item def _polygon_location(self, polygon_item): polygon = polygon_item.polygon attribute = polygon_item.attribute # Set text location text_locate, compare_list_y = [], [] for i in range(len(polygon) - 1): compare_list_y.append(polygon[i].toPoint().y()) top_of_polygon_y = min(compare_list_y) index = compare_list_y.index(top_of_polygon_y) text_locate.append(polygon[index].toPoint().x()) text_locate.append(polygon[index].toPoint().y()) text_locate[0] = text_locate[0] - 5 text_locate[1] = text_locate[1] - 5 # add text item if 'name' not in attribute: attribute['name'] = 'none' item = self.scene.addText(attribute['name'], QFont('Arial', 10)) self.items.append(item) polygon_item.text_item = item item.setPos(text_locate[0], text_locate[1]) def _polygon_add_info(self, polygon_item): """ polygon 을 metadata 에 추가한다. """ region_attribute = polygon_item.attribute polygon = polygon_item.polygon xs = [] ys = [] for point in polygon: xs.append(int(point.x())) ys.append(int(point.y())) polygon_item.id = self.metadata.add_polygon(region_attribute, xs, ys) def _polygon_delete(self, polygon_item): # delete polygon # 1. remove metadata print('0') print(polygon_item.id) self.metadata.delete_polygon(polygon_item.id) # 2. remove text item if polygon_item.text_item is not None: self.items.remove(polygon_item.text_item) self.scene.removeItem(polygon_item.text_item) # 3. remove polygon self.items.remove(polygon_item) self.scene.removeItem(polygon_item) def _mask_draw(self): if self.image is None: return # QBitmap 생성 bitmap = self.image.get_mask() # QPixmap 생성 pixmap = QPixmap(self.pixmap) pixmap.fill(self.mode.DRAW_MASK_COLOR) # pixmap 에 Mask를 씌운다. pixmap.setMask(bitmap) # Item 생성 및 추가 if self.obj['mask'] is not None: self.scene.removeItem(self.obj['mask']) self.obj['mask'] = QGraphicsPixmapItem(pixmap) self.scene.addItem(self.obj['mask']) def _mask_reset(self): # Reset Mask if self.image is not None: self.image.reset_mask() def _paint_draw_sign(self, x, y): # draw paint sign brush = QBrush(self.mode.DRAW_MASK_COLOR ) if self.mode.DRAW_PAINT_MODE else QBrush( QColor(255, 255, 255, 50)) pen = QPen(QColor(0, 0, 0), 3) # Item 생성 및 추가 width = self.mode.DRAW_PAINT_WIDTH if self.obj['paint'] is not None: self.scene.removeItem(self.obj['paint']) self.obj['paint'] = QGraphicsEllipseItem(int(x - width / 2), int(y - width / 2), width, width) self.obj['paint'].setBrush(brush) self.obj['paint'].setPen(pen) self.scene.addItem(self.obj['paint']) def _draw_sequence_press(self, pos, out_of_range): """ view 에 item 들을 그린다. mode 로 view 에 그리는 방법을 다르게 설정 할 수 있다. press, move, release sequence 함수들은 서로 의존적이다. mousePressEvent 에서 호출된다. """ if out_of_range: return last_pos = self.rect_p0 self.rect_p0 = pos # Mode 1. polygon draw if self.mode.CURRENT == 1: # 새로운 위치를 지정 및 기억한다. polygon_list = self.obj['polygon'] polygon_list.append(pos) # 조건을 확인하여 polygon 을 그린다. if self._polygon_check(polygon_list): # polygon list 를 이용하여 QPolygonF 객체 생성 polygon = QPolygonF([QPointF(point) for point in polygon_list]) # polygon 을 그린다. polygon_item = self._polygon_draw( polygon, self.mode.POLYGON_CURRENT_ATTRIBUTE) # metadata 추가 / id 할당 self._polygon_add_info(polygon_item) # polygon list 정보 삭제 polygon_list.clear() # lines 삭제 self._delete_items(self.obj['lines']) # 좌표 초기화 self.rect_p0 = None self.rect_p1 = None elif self.rect_p1 != None: # 임시 표시 직선을 그린다. pen = QPen(self.mode.DRAW_PEN_COLOR, self.mode.DRAW_PEN_WIDTH) if not out_of_range \ else QPen(self.mode.DRAW_PEN_COLOR_WARNNING, self.mode.DRAW_PEN_WIDTH) line = QLineF(self.rect_p0.x(), self.rect_p0.y(), last_pos.x(), last_pos.y()) self.obj['lines'].insert(0, self.scene.addLine(line, pen)) # Mode 2. Magic Wand elif self.mode.CURRENT == 2: # option 설정 if self.KEY_SHIFT and self.KEY_ALT: option = 'and' elif self.KEY_SHIFT: option = 'or' elif self.KEY_SHIFT: option = 'sub' else: option = 'select' # 현재 point mask 생성 self.image.set_tolerance(self.mode.DRAW_MASK_TOLERANCE) self.image.set_mask(int(pos.x()), int(pos.y()), option=option) # mask 를 그린다. self._mask_draw() # Mode 3. Paint elif self.mode.CURRENT == 3: # paint or erase if self.mode.DRAW_PAINT_MODE: self.image.paint_mask(int(pos.x()), int(pos.y()), self.mode.DRAW_PAINT_WIDTH) else: self.image.erase_mask(int(pos.x()), int(pos.y()), self.mode.DRAW_PAINT_WIDTH) self._mask_draw() self._paint_draw_sign(int(pos.x()), int(pos.y())) def _draw_sequence_move(self, pos, out_of_range): """ view 에 item 들을 그린다. mouseMoveEvent 에서 호출된다. """ # polygon draw if self.mode.CURRENT == 1: if self.rect_p0 is None: return else: self.rect_p1 = pos # QPen 선택 pen = QPen(self.mode.DRAW_PEN_COLOR, self.mode.DRAW_PEN_WIDTH) if not out_of_range \ else QPen(self.mode.DRAW_PEN_COLOR_WARNNING, self.mode.DRAW_PEN_WIDTH) # 마지막에 그렸던 직선 삭제 if len(self.obj['lines']) > 0: self.scene.removeItem(self.obj['lines'][-1]) del (self.obj['lines'][-1]) # 새로운 직선을 그린다 line = QLineF(self.rect_p0.x(), self.rect_p0.y(), self.rect_p1.x(), self.rect_p1.y()) self.obj['lines'].append(self.scene.addLine(line, pen)) elif self.mode.CURRENT == 3 and not out_of_range: # paint or erase if self.KEY_MOUSE_LEFT: if self.mode.DRAW_PAINT_MODE: self.image.paint_mask(int(pos.x()), int(pos.y()), self.mode.DRAW_PAINT_WIDTH) else: self.image.erase_mask(int(pos.x()), int(pos.y()), self.mode.DRAW_PAINT_WIDTH) self._mask_draw() self._paint_draw_sign(int(pos.x()), int(pos.y())) def _mouse_check(self, e): """ 현재 좌표와 좌표가 이미지 위에 위치하는지 여부를 반환한다. Image 가 할당된 이후에 호출될 수 있어야 한다. QMouseEvent 가 주어져야한다. """ assert self.image_item is not None, "No image to check" # 마우스 위치를 Scene 좌표로 변환한다. pos = self.mapToScene(e.pos()) # 변환된 Scene 좌표를 item 좌표로 변환한다. pos = self.image_item.mapFromScene(pos) # image item 좌표 범위를 QRectF 클래스로 받아온다. rect = self.image_item.boundingRect() # 좌표와 좌표 위치 정보 반환. return pos, not rect.contains(pos) def keyPressEvent(self, e): super().keyPressEvent(e) if self.image is None: return if e.key() == Qt.Key_Escape: self.last_mask = self.image.backup_mask() self._mask_reset() self.refresh() if e.key() == Qt.Key_Shift: self.KEY_SHIFT = True self.mode.DRAW_PAINT_MODE = not self.mode.DRAW_PAINT_MODE if e.key() == Qt.Key_Alt: self.KEY_ALT = True if e.key() == Qt.Key_Control: self.KEY_CTRL = True if e.key() == Qt.Key_Space and self.image is not None: # Mask To Polygon (with backup) self.last_mask = self.image.backup_mask() polygon = self.image.get_polygon() if polygon is not None: polygon_item = self._polygon_draw( polygon, self.mode.POLYGON_CURRENT_ATTRIBUTE) self._polygon_add_info(polygon_item) self.last_polygon = polygon_item self._mask_reset() self.refresh() def keyReleaseEvent(self, e): if e.key() == Qt.Key_Shift: self.KEY_SHIFT = False if e.key() == Qt.Key_Alt: self.KEY_ALT = False if e.key() == Qt.Key_Control: self.KEY_CTRL = False def showEvent(self, e): self._update() super().showEvent(e) def wheelEvent(self, e): if self.KEY_CTRL: if self.mode.CURRENT == 3: pos, out_of_range = self._mouse_check(e) if not out_of_range: # pain wheel self._paint_draw_sign(int(pos.x()), int(pos.y())) return # zoom in / out 구현 if e.angleDelta().y() > 0 and self._zoom < 10: self.scale(1.25, 1.25) self._zoom += 1 elif e.angleDelta().y() < 0 and self._zoom > -10: self.scale(0.8, 0.8) self._zoom -= 1 def mousePressEvent(self, e): # QGraphicsView.ScrollHandDrag middle mouse super().mousePressEvent(e) if self.KEY_CTRL: return # 좌표와 정보를 받아온다. if self.image_item is not None: pos, out_of_range = self._mouse_check(e) else: return # Reset Mask if self.mode.CURRENT not in [0, 2, 3]: self._mask_reset() self._mask_draw() # Left Button if e.button() == Qt.LeftButton: self.KEY_MOUSE_LEFT = True # 좌표를 이용하여 view 를 그린다. self._draw_sequence_press(pos, out_of_range) def mouseMoveEvent(self, e): super().mouseMoveEvent(e) # Left Button if self.image_item is not None: # 좌표와 정보를 받아온다. pos, out_of_range = self._mouse_check(e) # 좌표를 이용하여 view 를 그린다. self._draw_sequence_move(pos, out_of_range) def mouseReleaseEvent(self, e): super().mouseReleaseEvent(e) # 좌표와 정보를 받아온다. if self.image_item is not None: pos, out_of_range = self._mouse_check(e) else: return # Left Button if e.button() == Qt.LeftButton: self.KEY_MOUSE_LEFT = False