class RDisc(QObject): def __init__(self, r, x, y, color, line_color, line_width): self._radius = r self._pos = QPointF(x, y) super().__init__() # The supporting rectangle self.rect = QRectF(x - r, y - r, 2 * r, 2 * r) # The underlying QGraphicsEllipseItem self.disc = QGraphicsEllipseItem() self.disc.setRect(self.rect) self.disc.setBrush(QtGui.QBrush(color)) pen = QPen() pen.setWidthF(line_width) pen.setColor(line_color) self.disc.setPen(pen) self._visible = 1 def x(self): return self._pos.x() def y(self): return self._pos.y() # The following functions are for animation support @pyqtProperty(QPointF) def pos(self): return self._pos @pos.setter def pos(self, value): self.rect = QRectF(value.x() - self._radius, value.y() - self._radius, 2 * self._radius, 2 * self._radius) self.disc.setRect(self.rect) self._pos = value @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if (value > 0): self.disc.show() else: self.disc.hide() self._visible = value
class AbstractSliceTool(QGraphicsObject): """Summary Attributes: angles (TYPE): Description FILTER_NAME (str): Description is_started (bool): Description manager (TYPE): Description part_item (TYPE): Description sgv (TYPE): Description vectors (TYPE): Description """ _RADIUS = styles.SLICE_HELIX_RADIUS _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS) FILTER_NAME = 'virtual_helix' # _CENTER_OF_HELIX = QPointF(0. 0.) """Abstract base class to be subclassed by all other pathview tools.""" def __init__(self, manager): """Summary Args: manager (TYPE): Description """ super(AbstractSliceTool, self).__init__(parent=manager.viewroot) """ Pareting to viewroot to prevent orphan _line_item from occuring """ self.sgv = None self.manager = manager self._active = False self._last_location = None self._line_item = QGraphicsLineItem(self) self._line_item.hide() self._vhi = None self.hide() self.is_started = False self.angles = [math.radians(x) for x in range(0, 360, 30)] self.vectors = self.setVectors() self.part_item = None self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self) self.vhi_hint_item.setPen(_MOD_PEN) self.vhi_hint_item.setZValue(styles.ZPARTITEM) # end def ######################## Drawing ####################################### def setVectors(self): """Summary Returns: TYPE: Description """ rad = self._RADIUS return [QLineF(rad, rad, rad*(1. + 2.*math.cos(x)), rad*(1. + 2.*math.sin(x)) ) for x in self.angles] # end def def setVirtualHelixItem(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ rad = self._RADIUS self._vhi = virtual_helix_item li = self._line_item li.setParentItem(virtual_helix_item) li.setLine(rad, rad, rad, rad) # li.setLine(0., 0., 0., 0.) # end def def setSelectionFilter(self, filter_name_list): if 'virtual_helix' in filter_name_list: self.vhi_hint_item.setPen(_MOD_PEN) else: self.vhi_hint_item.setPen(_INACTIVE_PEN) # end def def resetTool(self): """Summary Returns: TYPE: Description """ self._line_item.setParentItem(self) def idNum(self): """Summary Returns: TYPE: Description """ if self._vhi is not None: return self._vhi.idNum() def setPartItem(self, part_item): """Summary Args: part_item (TYPE): Description Returns: TYPE: Description """ self.vhi_hint_item.setParentItem(part_item) self.part_item = part_item # end def def boundingRect(self): """Required to prevent NotImplementedError() """ return QRectF() def eventToPosition(self, part_item, event): """take an event and return a position as a QPointF update widget as well Args: part_item (TYPE): Description event (TYPE): Description """ if self.is_started: pos = self.findNearestPoint(part_item, event.scenePos()) else: pos = event.pos() self.vhi_hint_item.setPos( pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) return pos # end def def setHintPos(self, pos): self.vhi_hint_item.setPos( pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) # end def def findNearestPoint(self, part_item, target_scenepos): """ Args: part_item (TYPE): Description target_scenepos (TYPE): Description """ li = self._line_item pos = li.mapFromScene(target_scenepos) line = li.line() mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos) # Check if the click happened on the origin VH if mouse_point_vec.length() < self._RADIUS: # return part_item.mapFromScene(target_scenepos) return None angle_min = 9999 direction_min = None for vector in self.vectors: angle_new = mouse_point_vec.angleTo(vector) if angle_new < angle_min: direction_min = vector angle_min = angle_new if direction_min is not None: li.setLine(direction_min) return part_item.mapFromItem(li, direction_min.p2()) else: print("default point") line.setP2(pos) li.setLine(line) return part_item.mapFromItem(li, pos) # end def def findNextPoint(self, part_item, target_part_pos): """ Args: part_item (TYPE): Description target_part_pos (TYPE): Description """ li = self._line_item pos = li.mapFromItem(part_item, target_part_pos) for i, vector in enumerate(self.vectors): if vector.p2() == pos: return part_item.mapFromItem(li, self.vectors[i - 1].p2()) # origin VirtualHelixItem is overlapping destination VirtualHelixItem return part_item.mapFromItem(li, self.vectors[0].p2()) # end def def hideLineItem(self): """Summary Returns: TYPE: Description """ self.vhi_hint_item.hide() li = self._line_item li.hide() li.setParentItem(self) line = li.line() line.setP2(self._CENTER_OF_HELIX) li.setLine(line) # li.hide() self.is_started = False # end def # def hoverEnterEvent(self, event): # self.vhi_hint_item.show() # #print("Slice VHI hoverEnterEvent") # # def hoverMoveEvent(self, event): # # print("Slice VHI hoverMoveEvent") # def hoverLeaveEvent(self, event): # # self.vhi_hint_item.hide() # #print("Slice VHI hoverLeaveEvent") def hoverMoveEvent(self, part_item, event): """Summary Args: part_item (TYPE): Description event (TYPE): Description Returns: TYPE: Description """ # self.vhi_hint_item.setPos( event.pos()- # QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) pos = self.eventToPosition(part_item, event) return pos # end def def setActive(self, will_be_active, old_tool=None): """ Called by SliceToolManager.setActiveTool when the tool becomes active. Used, for example, to show/hide tool-specific ui elements. Args: will_be_active (TYPE): Description old_tool (None, optional): Description """ if self._active and not will_be_active: self.deactivate() self._active = will_be_active self.sgv = self.manager.window.slice_graphics_view if hasattr(self, 'getCustomContextMenu'): # print("connecting ccm") try: # Hack to prevent multiple connections self.sgv.customContextMenuRequested.disconnect() except: pass self.sgv.customContextMenuRequested.connect(self.getCustomContextMenu) # end def def deactivate(self): """Summary Returns: TYPE: Description """ if hasattr(self, 'getCustomContextMenu'): # print("disconnecting ccm") self.sgv.customContextMenuRequested.disconnect(self.getCustomContextMenu) self.sgv = None self.is_started = False self.hideLineItem() self._vhi = None self.part_item = None self.hide() self._active = False # end def def isActive(self): """Returns isActive """ return self._active
class AbstractSliceTool(QGraphicsObject): """Summary Attributes: angles (TYPE): Description FILTER_NAME (str): Description is_started (bool): Description manager (TYPE): Description part_item (TYPE): Description sgv (TYPE): Description vectors (TYPE): Description """ _RADIUS = styles.SLICE_HELIX_RADIUS _CENTER_OF_HELIX = QPointF(_RADIUS, _RADIUS) FILTER_NAME = 'virtual_helix' # _CENTER_OF_HELIX = QPointF(0. 0.) """Abstract base class to be subclassed by all other pathview tools.""" def __init__(self, manager): """Summary Args: manager (TYPE): Description """ super(AbstractSliceTool, self).__init__(parent=manager.viewroot) """ Pareting to viewroot to prevent orphan _line_item from occuring """ self.sgv = None self.manager = manager self._active = False self._last_location = None self._line_item = QGraphicsLineItem(self) self._line_item.hide() self._vhi = None self.hide() self.is_started = False self.angles = [math.radians(x) for x in range(0, 360, 30)] self.vectors = self.setVectors() self.part_item = None self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self) self.vhi_hint_item.setPen(_MOD_PEN) self.vhi_hint_item.setZValue(styles.ZPARTITEM) # end def ######################## Drawing ####################################### def setVectors(self): """Summary Returns: TYPE: Description """ rad = self._RADIUS return [ QLineF(rad, rad, rad * (1. + 2. * math.cos(x)), rad * (1. + 2. * math.sin(x))) for x in self.angles ] # end def def setVirtualHelixItem(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ rad = self._RADIUS self._vhi = virtual_helix_item li = self._line_item li.setParentItem(virtual_helix_item) li.setLine(rad, rad, rad, rad) # li.setLine(0., 0., 0., 0.) # end def def setSelectionFilter(self, filter_name_list): if 'virtual_helix' in filter_name_list: self.vhi_hint_item.setPen(_MOD_PEN) else: self.vhi_hint_item.setPen(_INACTIVE_PEN) # end def def resetTool(self): """Summary Returns: TYPE: Description """ self._line_item.setParentItem(self) def idNum(self): """Summary Returns: TYPE: Description """ if self._vhi is not None: return self._vhi.idNum() def setPartItem(self, part_item): """Summary Args: part_item (TYPE): Description Returns: TYPE: Description """ self.vhi_hint_item.setParentItem(part_item) self.part_item = part_item # end def def boundingRect(self): """Required to prevent NotImplementedError() """ return QRectF() def eventToPosition(self, part_item, event): """take an event and return a position as a QPointF update widget as well Args: part_item (TYPE): Description event (TYPE): Description """ if self.is_started: pos = self.findNearestPoint(part_item, event.scenePos()) else: pos = event.pos() self.vhi_hint_item.setPos(pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) return pos # end def def setHintPos(self, pos): self.vhi_hint_item.setPos(pos - QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) # end def def findNearestPoint(self, part_item, target_scenepos): """ Args: part_item (TYPE): Description target_scenepos (TYPE): Description """ li = self._line_item pos = li.mapFromScene(target_scenepos) line = li.line() mouse_point_vec = QLineF(self._CENTER_OF_HELIX, pos) # Check if the click happened on the origin VH if mouse_point_vec.length() < self._RADIUS: # return part_item.mapFromScene(target_scenepos) return None angle_min = 9999 direction_min = None for vector in self.vectors: angle_new = mouse_point_vec.angleTo(vector) if angle_new < angle_min: direction_min = vector angle_min = angle_new if direction_min is not None: li.setLine(direction_min) return part_item.mapFromItem(li, direction_min.p2()) else: print("default point") line.setP2(pos) li.setLine(line) return part_item.mapFromItem(li, pos) # end def def findNextPoint(self, part_item, target_part_pos): """ Args: part_item (TYPE): Description target_part_pos (TYPE): Description """ li = self._line_item pos = li.mapFromItem(part_item, target_part_pos) for i, vector in enumerate(self.vectors): if vector.p2() == pos: return part_item.mapFromItem(li, self.vectors[i - 1].p2()) # origin VirtualHelixItem is overlapping destination VirtualHelixItem return part_item.mapFromItem(li, self.vectors[0].p2()) # end def def hideLineItem(self): """Summary Returns: TYPE: Description """ self.vhi_hint_item.hide() li = self._line_item li.hide() li.setParentItem(self) line = li.line() line.setP2(self._CENTER_OF_HELIX) li.setLine(line) # li.hide() self.is_started = False # end def # def hoverEnterEvent(self, event): # self.vhi_hint_item.show() # #print("Slice VHI hoverEnterEvent") # # def hoverMoveEvent(self, event): # # print("Slice VHI hoverMoveEvent") # def hoverLeaveEvent(self, event): # # self.vhi_hint_item.hide() # #print("Slice VHI hoverLeaveEvent") def hoverMoveEvent(self, part_item, event): """Summary Args: part_item (TYPE): Description event (TYPE): Description Returns: TYPE: Description """ # self.vhi_hint_item.setPos( event.pos()- # QPointF(_RADIUS - DELTA, _RADIUS - DELTA)) pos = self.eventToPosition(part_item, event) return pos # end def def setActive(self, will_be_active, old_tool=None): """ Called by SliceToolManager.setActiveTool when the tool becomes active. Used, for example, to show/hide tool-specific ui elements. Args: will_be_active (TYPE): Description old_tool (None, optional): Description """ if self._active and not will_be_active: self.deactivate() self._active = will_be_active self.sgv = self.manager.window.slice_graphics_view if hasattr(self, 'getCustomContextMenu'): # print("connecting ccm") try: # Hack to prevent multiple connections self.sgv.customContextMenuRequested.disconnect() except: pass self.sgv.customContextMenuRequested.connect( self.getCustomContextMenu) # end def def deactivate(self): """Summary Returns: TYPE: Description """ if hasattr(self, 'getCustomContextMenu'): # print("disconnecting ccm") self.sgv.customContextMenuRequested.disconnect( self.getCustomContextMenu) self.sgv = None self.is_started = False self.hideLineItem() self._vhi = None self.part_item = None self.hide() self._active = False # end def def isActive(self): """Returns isActive """ return self._active
class StickWidget(QGraphicsObject): font: QFont = QFont("monospace", 32) delete_clicked = pyqtSignal(Stick) link_initiated = pyqtSignal('PyQt_PyObject') # Actually StickWidget link_accepted = pyqtSignal('PyQt_PyObject') hovered = pyqtSignal(['PyQt_PyObject', 'PyQt_PyObject']) stick_changed = pyqtSignal('PyQt_PyObject') sibling_changed = pyqtSignal(bool) right_clicked = pyqtSignal('PyQt_PyObject') handle_idle_brush = QBrush(QColor(0, 125, 125, 50)) handle_hover_brush = QBrush(QColor(125, 125, 0, 50)) handle_press_brush = QBrush(QColor(200, 200, 0, 0)) handle_idle_pen = QPen(QColor(0, 0, 0, 255)) handle_press_pen = QPen(QColor(200, 200, 0, 255)) handle_size = 20 normal_color = QColor(0, 200, 120) negative_color = QColor(200, 0, 0) positive_color = QColor(0, 200, 0) mismatched = pyqtSignal('PyQt_PyObject') misplaced = pyqtSignal('PyQt_PyObject') measurement_corrected = pyqtSignal('PyQt_PyObject') clearly_visible = pyqtSignal('PyQt_PyObject') zero_clicked = pyqtSignal('PyQt_PyObject') def __init__(self, stick: Stick, camera: Camera, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.camera = camera self.stick = stick self.line = QLineF() self.gline = QGraphicsLineItem(self.line) self.stick_label_text = QGraphicsSimpleTextItem("0", self) self.stick_label_text.setFont(StickWidget.font) self.stick_label_text.setPos(self.line.p1() - QPoint(0, 24)) self.stick_label_text.setBrush(QBrush(QColor(0, 255, 0))) self.stick_label_text.hide() self.setZValue(10) self.mode = StickMode.Display self.btn_delete = Button("delete", "x", parent=self) self.btn_delete.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.btn_delete.set_base_color([ButtonColor.RED]) self.btn_delete.setVisible(False) btn_size = max(int(np.linalg.norm(self.stick.top - self.stick.bottom) / 5.0), 15) self.btn_delete.set_height(12) self.btn_delete.clicked.connect(self.handle_btn_delete_clicked) self.btn_delete.setPos(self.line.p1() - QPointF(0.5 * self.btn_delete.boundingRect().width(), 1.1 * self.btn_delete.boundingRect().height())) self.btn_delete.set_opacity(0.7) self.top_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.mid_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.bottom_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.top_handle.setAcceptedMouseButtons(Qt.NoButton) self.mid_handle.setAcceptedMouseButtons(Qt.NoButton) self.bottom_handle.setAcceptedMouseButtons(Qt.NoButton) self.top_handle.setBrush(self.handle_idle_brush) self.top_handle.setPen(self.handle_idle_pen) self.mid_handle.setBrush(self.handle_idle_brush) self.mid_handle.setPen(self.handle_idle_pen) self.bottom_handle.setBrush(self.handle_idle_brush) self.bottom_handle.setPen(self.handle_idle_pen) self.hovered_handle: Optional[QGraphicsRectItem] = None self.handles = [self.top_handle, self.mid_handle, self.bottom_handle] self.link_button = Button("link", "Link to...", parent=self) self.link_button.set_base_color([ButtonColor.GREEN]) self.link_button.set_height(12) self.link_button.set_label("Link", direction="vertical") self.link_button.fit_to_contents() self.link_button.clicked.connect(lambda: self.link_initiated.emit(self)) self.link_button.setVisible(False) self.link_button.setFlag(QGraphicsObject.ItemIgnoresTransformations, False) self.adjust_line() self.setAcceptHoverEvents(True) self.top_handle.setZValue(4) self.bottom_handle.setZValue(4) self.mid_handle.setZValue(4) self.top_handle.hide() self.mid_handle.hide() self.bottom_handle.hide() self.handle_mouse_offset = QPointF(0, 0) self.available_for_linking = False self.link_source = False self.current_highlight_color: QColor = StickWidget.normal_color self.highlighted = False self.frame_color: Optional[None] = self.normal_color self.is_linked = False self.is_master = True self.selected = False self.measured_height: int = -1 self.current_color = self.normal_color self.show_label = False self.highlight_animation = QPropertyAnimation(self, b"highlight_color") self.highlight_animation.valueChanged.connect(self.handle_highlight_animation_value_changed) self.deleting = False self.update_tooltip() self.show_measurements: bool = False self.proposed_snow_height: int = -1 self.zero_btn = Button("zero_btn", "0", parent=self) self.zero_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.zero_btn.setVisible(False) self.zero_btn.setPos(self.boundingRect().center() + QPointF(self.zero_btn.boundingRect().width() * -0.5, self.boundingRect().height() * 0.5)) self.zero_btn.clicked.connect(self.handle_zero) @pyqtSlot() def handle_btn_delete_clicked(self): self.delete_clicked.emit(self.stick) def prepare_for_deleting(self): self.deleting = True self.highlight_animation.stop() self.btn_delete.setParentItem(None) self.scene().removeItem(self.btn_delete) self.btn_delete.deleteLater() def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[PyQt5.QtWidgets.QWidget] = ...): painter.setPen(QPen(self.current_color, 1.0)) brush = QBrush(self.current_highlight_color) pen = QPen(brush, 4) painter.setPen(pen) if self.highlighted: painter.fillRect(self.boundingRect(), QBrush(self.current_highlight_color)) if self.frame_color is not None and self.mode != StickMode.Edit and self.mode != StickMode.EditDelete: painter.setPen(QPen(self.frame_color, 4)) painter.drawRect(self.boundingRect()) pen = QPen(QColor(0, 255, 0, 255)) pen.setWidth(1.0) pen.setColor(QColor(255, 0, 255, 255)) pen.setStyle(Qt.DotLine) painter.setPen(pen) off = 10 painter.drawLine(self.line.p1() - QPointF(0, off), self.line.p1() + QPointF(0, off)) painter.drawLine(self.line.p1() - QPointF(off, 0), self.line.p1() + QPointF(off, 0)) painter.drawLine(self.line.p2() - QPointF(0, off), self.line.p2() + QPointF(0, off)) painter.drawLine(self.line.p2() - QPointF(off, 0), self.line.p2() + QPointF(off, 0)) pen.setStyle(Qt.SolidLine) pen.setColor(QColor(0, 255, 0, 255)) painter.setPen(pen) if self.mode != StickMode.EditDelete: pen.setWidth(2.0) br = painter.brush() painter.setPen(pen) painter.drawEllipse(self.line.p1(), 10, 10) painter.drawEllipse(self.line.p2(), 10, 10) painter.setBrush(br) if self.mode == StickMode.Measurement and self.proposed_snow_height >= 0: point = QPointF(self.boundingRect().x(), -self.proposed_snow_height + self.line.p2().y()) pen = QPen(QColor(200, 100, 0, 255), 3.0) painter.setPen(pen) painter.drawLine(point, point + QPointF(self.boundingRect().width(), 0.0)) if self.measured_height >= 0: vec = (self.stick.top - self.stick.bottom) / np.linalg.norm(self.stick.top - self.stick.bottom) dist_along_stick = self.measured_height / np.dot(np.array([0.0, -1.0]), vec) point = self.line.p2() + dist_along_stick * QPointF(vec[0], vec[1]) point = QPointF(self.boundingRect().x(), point.y()) pen = QPen(QColor(0, 100, 200, 255), 3.0) painter.setPen(pen) painter.drawLine(point, point + QPointF(self.boundingRect().width(), 0.0)) else: painter.drawLine(self.line.p1(), self.line.p2()) if self.selected: pen.setColor(QColor(255, 125, 0, 255)) pen.setStyle(Qt.DashLine) painter.setPen(pen) painter.drawRect(self.boundingRect().marginsAdded(QMarginsF(5, 5, 5, 5))) if self.show_measurements: painter.fillRect(self.stick_label_text.boundingRect().translated(self.stick_label_text.pos()), QBrush(QColor(0, 0, 0, 120))) def boundingRect(self) -> PyQt5.QtCore.QRectF: return self.gline.boundingRect().united(self.top_handle.boundingRect()).\ united(self.mid_handle.boundingRect()).united(self.bottom_handle.boundingRect()) def set_edit_mode(self, value: bool): if value: self.set_mode(StickMode.EditDelete) else: self.set_mode(StickMode.Display) def set_mode(self, mode: StickMode): if mode == StickMode.Display: self.btn_delete.setVisible(False) self.top_handle.setVisible(False) self.mid_handle.setVisible(False) self.bottom_handle.setVisible(False) self.link_button.setVisible(False) self.available_for_linking = False self.link_source = False self.zero_btn.setVisible(False) self.setVisible(self.stick.is_visible) elif mode == StickMode.EditDelete: self.set_mode(StickMode.Display) self.top_handle.setVisible(True) self.mid_handle.setVisible(True) self.bottom_handle.setVisible(True) self.available_for_linking = False self.link_source = False self.btn_delete.setVisible(True) elif mode == StickMode.LinkSource: self.set_mode(StickMode.Display) self.link_source = True self.available_for_linking = False self.link_button.setPos(self.boundingRect().topLeft()) self.link_button.set_width(int(self.boundingRect().width())) self.link_button.set_button_height(int(self.boundingRect().height())) self.link_button.adjust_text_to_button() elif mode == StickMode.LinkTarget: self.set_mode(StickMode.Display) self.link_source = False self.available_for_linking = True elif mode == StickMode.Edit: self.set_mode(StickMode.EditDelete) self.btn_delete.setVisible(False) elif mode == StickMode.Measurement: self.zero_btn.setVisible(True) self.setVisible(True) self.mode = mode self.update_tooltip() self.update() def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.mode != StickMode.EditDelete: return if self.hovered_handle is None: return self.hovered_handle.setBrush(self.handle_press_brush) if self.hovered_handle == self.mid_handle: self.bottom_handle.setBrush(self.handle_press_brush) self.bottom_handle.setPen(self.handle_press_pen) self.bottom_handle.setOpacity(0.5) self.top_handle.setBrush(self.handle_press_brush) self.top_handle.setPen(self.handle_press_pen) self.top_handle.setOpacity(0.5) self.hovered_handle.setPen(self.handle_press_pen) self.hovered_handle.setOpacity(0.5) self.handle_mouse_offset = self.hovered_handle.rect().center() - event.pos() self.btn_delete.setVisible(False) def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.available_for_linking: self.link_accepted.emit(self) return if self.mode == StickMode.Measurement: old_snow = self.stick.snow_height_px self.measured_height = self.proposed_snow_height self.stick.set_snow_height_px(self.proposed_snow_height) if abs(old_snow - self.proposed_snow_height) > 0: self.measurement_corrected.emit(self) self.proposed_snow_height = -1 if self.mode != StickMode.EditDelete and self.mode != StickMode.Edit: return if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_hover_brush) self.hovered_handle.setPen(self.handle_idle_pen) self.hovered_handle.setOpacity(1.0) if self.hovered_handle == self.mid_handle: self.bottom_handle.setBrush(self.handle_idle_brush) self.bottom_handle.setPen(self.handle_idle_pen) self.bottom_handle.setOpacity(1.0) self.top_handle.setBrush(self.handle_idle_brush) self.top_handle.setPen(self.handle_idle_pen) self.top_handle.setOpacity(1.0) self.stick_changed.emit(self) self.hovered_handle = None if self.mode == StickMode.EditDelete: self.btn_delete.setVisible(True) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): if self.hovered_handle is None: return if self.hovered_handle == self.top_handle: self.line.setP1((event.pos() + self.handle_mouse_offset).toPoint()) elif self.hovered_handle == self.bottom_handle: self.line.setP2((event.pos() + self.handle_mouse_offset).toPoint()) else: displacement = event.pos() - event.lastPos() self.setPos(self.pos() + displacement) self.adjust_handles() self.adjust_stick() self.scene().update() def set_top(self, pos: QPoint): self.line.setP1(pos) self.adjust_handles() self.adjust_stick() self.scene().update() def set_bottom(self, pos: QPoint): self.line.setP2(pos) self.adjust_handles() self.adjust_stick() self.scene().update() def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): if self.available_for_linking: self.hovered.emit(True, self) elif self.link_source: self.link_button.setVisible(True) self.scene().update() def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): for h in self.handles: h.setBrush(self.handle_idle_brush) self.hovered_handle = None if self.available_for_linking: self.hovered.emit(False, self) self.link_button.setVisible(False) self.proposed_snow_height = -1 self.scene().update() def hoverMoveEvent(self, event: QGraphicsSceneHoverEvent): if self.mode != StickMode.EditDelete and self.mode != StickMode.Edit and self.mode != StickMode.Measurement: return if self.mode == StickMode.Measurement: self.proposed_snow_height = max(self.line.p2().y() - event.pos().y(), 0) self.update() return hovered_handle = list(filter(lambda h: h.rect().contains(event.pos()), self.handles)) if len(hovered_handle) == 0: if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle = None return if self.hovered_handle is not None and self.hovered_handle != hovered_handle[0]: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle = hovered_handle[0] if self.hovered_handle == self.top_handle: self.top_handle.setBrush(self.handle_hover_brush) elif self.hovered_handle == self.bottom_handle: self.bottom_handle.setBrush(self.handle_hover_brush) else: self.mid_handle.setBrush(self.handle_hover_brush) self.scene().update() def adjust_stick(self): self.stick.top[0] = self.pos().x() + self.line.p1().x() self.stick.top[1] = self.pos().y() + self.line.p1().y() self.stick.bottom[0] = self.pos().x() + self.line.p2().x() self.stick.bottom[1] = self.pos().y() + self.line.p2().y() def adjust_handles(self): if self.line.p1().y() > self.line.p2().y(): p1, p2 = self.line.p1(), self.line.p2() self.line.setP1(p2) self.line.setP2(p1) if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle.setPen(self.handle_idle_pen) self.hovered_handle = self.top_handle if self.hovered_handle == self.bottom_handle else self.bottom_handle self.hovered_handle.setBrush(self.handle_press_brush) self.hovered_handle.setPen(self.handle_press_pen) rect = self.top_handle.rect() rect.moveCenter(self.line.p1()) self.top_handle.setRect(rect) rect = self.bottom_handle.rect() rect.moveCenter(self.line.p2()) self.bottom_handle.setRect(rect) rect = self.mid_handle.rect() rect.moveCenter(self.line.center()) self.mid_handle.setRect(rect) self.btn_delete.setPos(self.top_handle.rect().center() - QPointF(self.btn_delete.boundingRect().width() / 2, self.btn_delete.boundingRect().height() + self.top_handle.boundingRect().height() / 2)) def set_available_for_linking(self, available: bool): self.available_for_linking = available def set_is_link_source(self, is_source: bool): self.link_source = is_source self.link_button.setPos(self.boundingRect().topLeft()) self.link_button.set_width(int(self.boundingRect().width())) self.link_button.set_button_height(int(self.boundingRect().height())) self.link_button.adjust_text_to_button() def set_frame_color(self, color: Optional[QColor]): self.frame_color = color if color is not None else self.normal_color self.update() def set_is_linked(self, value: bool): self.is_linked = value if not self.is_linked: self.set_frame_color(None) if self.available_for_linking: self.highlight(QColor(0, 255, 0, 100)) else: self.highlight(None) self.update_tooltip() def adjust_line(self): self.setPos(QPointF(0.5 * (self.stick.top[0] + self.stick.bottom[0]), 0.5 * (self.stick.top[1] + self.stick.bottom[1]))) vec = 0.5 * (self.stick.top - self.stick.bottom) self.line.setP1(QPointF(vec[0], vec[1])) self.line.setP2(-self.line.p1()) self.gline.setLine(self.line) self.adjust_handles() self.stick_label_text.setPos(self.line.p1() - QPointF(0.5 * self.stick_label_text.boundingRect().width(), 1.3 * self.stick_label_text.boundingRect().height())) self.update() def set_selected(self, selected: bool): self.selected = selected self.update() def is_selected(self) -> bool: return self.selected def set_snow_height(self, height: int): self.measured_height = height self.update() def border_normal(self): self.current_color = self.normal_color self.update() def border_positive(self): self.current_color = self.positive_color self.update() def border_negative(self): self.current_color = self.negative_color self.update() @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 highlight(self, color: Optional[QColor], animated: bool = False): self.highlighted = color is not None if not animated or color is None: self.highlight_animation.stop() self.current_highlight_color = self.normal_color if color is None else color self.update() return self.highlight_animation.setStartValue(color) self.highlight_animation.setEndValue(color) self.highlight_animation.setKeyValueAt(0.5, color.darker()) self.highlight_animation.setDuration(2000) self.highlight_animation.setLoopCount(-1) self.highlight_animation.start() def handle_link_button_hovered(self, btn: Dict[str, Any]): self.link_button.setVisible(btn['hovered']) def handle_highlight_animation_value_changed(self, new: QColor): if not self.deleting: self.update(self.boundingRect().marginsAdded(QMarginsF(10, 10, 10, 10))) def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: self.right_clicked.emit({'stick_widget': self}) def set_stick_label(self, label: str): self.stick.label = label self.stick_label_text.setText(label) self.update_tooltip() self.update() def get_stick_label(self) -> str: return self.stick.label def get_stick_length_cm(self) -> int: return self.stick.length_cm def set_stick_length_cm(self, length: int): self.stick.length_cm = length self.update_tooltip() self.update() def update_tooltip(self): if self.mode != StickMode.Display or self.mode == StickMode.Measurement: self.setToolTip("") return snow_txt = "Snow height: " if self.stick.snow_height_px >= 0: snow_txt += str(self.stick.snow_height_cm) + " cm" self.stick_label_text.setText(str(self.stick.snow_height_cm)) else: snow_txt = "not measured" self.stick_label_text.setVisible(False) self.stick_label_text.setText(self.stick.label) self.stick_label_text.setVisible(True) stick_view_text = '' role = '' if self.stick.alternative_view is not None: alt_view = self.stick.alternative_view role = " - primary" alt = "Secondary" if not self.stick.primary: role = " - secondary" alt = "Primary" stick_view_text = f'\n{alt} view: {alt_view.label} in {alt_view.camera_folder.name}\n' mark = '*' if self.stick.determines_quality else '' self.setToolTip(f'{mark}{self.stick.label}{role}{stick_view_text}\nLength: {self.stick.length_cm} cm\n{snow_txt}') def set_stick(self, stick: Stick): self.reset_d_btns() self.stick = stick self.adjust_line() self.adjust_handles() self.set_snow_height(stick.snow_height_px) self.update_tooltip() self.set_show_measurements(self.show_measurements) if self.mode == StickMode.Measurement: self.set_frame_color(QColor(200, 100, 0, 100) if not self.stick.is_visible else None) self.setVisible(True) self.clearly_visible_btn.setVisible(not self.stick.is_visible) else: self.setVisible(self.stick.is_visible) def set_show_measurements(self, show: bool): self.show_measurements = show if self.show_measurements: self.stick_label_text.setText(str(self.stick.snow_height_cm) if self.stick.snow_height_cm >= 0 else "n/a") else: self.stick_label_text.setText(self.stick.label) self.update() def handle_zero(self): self.measured_height = 0 self.stick.set_snow_height_px(0) self.measurement_corrected.emit(self) def reset_d_btns(self): self.zero_btn.set_default_state()
class Minion(QGraphicsPixmapItem): def __init__(self, equipo, tamaño="pequeño"): self.equipo = equipo self.tamaño = tamaño super().__init__() if tamaño == "pequeño": self.setPixmap(QPixmap("Minions/minion0.png").scaled(30, 30)) self.setTransformOriginPoint(15, 15) else: self.setPixmap(QPixmap("Minions/minion0.png").scaled(40, 40)) self.setTransformOriginPoint(20, 20) paths = ["minion{}".format(i) for i in range(0, 9)] paths = sorted([p for i in range(3) for p in paths]) self.img = self.gen_path(paths) # Area de ataque if tamaño == "pequeño": self.area = QGraphicsEllipseItem(-5, -5, 40, 40) self.area.setTransformOriginPoint(20, 20) else: self.area = QGraphicsEllipseItem(-20, -20, 80, 80) self.area.setTransformOriginPoint(40, 40) self.area.setParentItem(self) self.area.hide() # La barra de vida if tamaño == "pequeño": self.barra_vida = BarraVida(30, 45) else: self.barra_vida = BarraVida(40, 60) self.barra_vida.setParentItem(self) # Para cuando ataquen self.target = None # Stats if self.tamaño == "pequeño": self.vida = 45 self.daño = 2 self.pix = 30 else: self.vida = 60 self.daño = 4 self.pix = 40 self.mover = QtCore.QTimer() self.mover.timeout.connect(self.move) # Se mueve una vez cada 50 milisegundos self.mover.start(50) # Timer de ataque self.ataque = QtCore.QTimer() self.ataque.timeout.connect(self.atacar) self.hover = False def move(self): img = next(self.img) if self.hover is False: self.setPixmap(QPixmap(img).scaled(self.pix, self.pix)) else: self.setPixmap(QPixmap(img + "_hover").scaled(self.pix, self.pix)) meta = self.mas_cercano(self.enemigos) x = self.x() + 10 - meta[0] y = self.y() + 10 - meta[1] self.angulo = -math.degrees(math.atan2(x, y)) if self.angulo < 0: self.angulo += 360 self.rotacion(self.angulo + 180) self.setPos(self.x() + 0.4 * math.sin(math.radians(180 - self.angulo)), self.y() + 0.4 * math.cos(math.radians(180 - self.angulo))) # Si hay enemigos en el area empieza a atacar if len([ i for i in self.area.collidingItems() if "equipo" in i.__dict__ and i.equipo != self.equipo ]) > 0: self.mover.stop() self.setPixmap( QPixmap("Minions/minion0.png").scaled(self.pix, self.pix)) self.ataque.start(1000) def rotacion(self, angulo): self.setRotation(angulo) self.barra_vida.setRotation(-angulo) def mas_cercano(self, lista): items = [(i.x(), i.y()) for i in lista] distancias = [self.dist(i) for i in items] return items[distancias.index(min(distancias))] def dist(self, punto): distancia = math.sqrt((punto[0] - self.x())**2 + (punto[1] - self.y())**2) return distancia def gen_path(self, lista): while True: for path in lista: yield "Minions/{}".format(path) def atacar(self): if self.target is None: enemigos = [ i for i in self.area.collidingItems() if "equipo" in i.__dict__ and i.equipo != self.equipo ] if len(enemigos) > 0: self.target = enemigos[0] self.target.recibir_daño(self.daño) # Esto para eliminar un bug que dejaba como target a objetos que ya no estaban en la scene (porque habian muerto) elif self.target not in self.scene().items(): self.target = None else: self.target.recibir_daño(self.daño) if self.scene() and len([ i for i in self.area.collidingItems() if "equipo" in i.__dict__ and i.equipo != self.equipo ]) == 0: self.target = None self.ataque.stop() self.move() self.mover.start(50) def recibir_daño(self, daño): self.vida -= daño self.barra_vida.update(self.vida) if self.vida <= 0 and self.scene(): self.scene().removeItem(self) self.ataque.stop() self.mover.stop() self.target = None def hoverEntra(self, evento): # Solo los enemigos son atacables if self.equipo == 2: self.hover = True if self.ataque.isActive(): self.setPixmap( QPixmap("Minions/minion0_hover.png").scaled( self.pix, self.pix)) def hoverSale(self, evento): self.hover = False if self.ataque.isActive(): self.setPixmap( QPixmap("Minions/minion0.png").scaled(self.pix, self.pix)) @property def enemigos(self): if self.scene(): return [ i for i in self.scene().items() if "equipo" in i.__dict__ and i.equipo != self.equipo ]
class Player(QGraphicsPixmapItem): def __init__(self, champ): super().__init__() self.setPos(200, 250) # Valores Utiles despues self.angulo = 0 self.mouse = (0, 0) self.equipo = 1 self.nombre = champ self.target = None self.muerto = False self.muertes = 0 # Stats if champ == "Chau": self.setPixmap(QPixmap("Player/Chau/player_0.png").scaled(50, 50)) paths = ["player_{}".format(i) for i in range(8)] paths = sorted([p for i in range(3) for p in paths]) self.vida = 500 self.rango = 40 self.velocidad = 30 self.v_atk = 10 self.daño = 5 elif champ == "Hernan": self.setPixmap(QPixmap("Player/Hernan/peludo0.png").scaled(50, 50)) paths = ["peludo{}".format(i) for i in range(16)] paths = sorted([p for i in range(3) for p in paths]) self.vida = 666 self.rango = 5 self.velocidad = 10 self.v_atk = 10 self.daño = 20 elif champ == "Roberto": self.setPixmap( QPixmap("Player/Roberto/player0.png").scaled(50, 50)) paths = [ "player1", "player2", "player3", "player4", "player5", "player0" ] paths = sorted([p for i in range(5) for p in paths]) self.vida = 200 self.rango = 70 self.velocidad = 15 self.v_atk = 20 self.daño = 10 # El generador de paths self.img = self.gen_path(paths) self.setTransformOriginPoint(25, 25) # Area de ataque self.area = QGraphicsEllipseItem(-self.rango, -self.rango, 50 + self.rango * 2, 50 + self.rango * 2) self.area.setParentItem(self) self.area.hide() # La barra de vida self.barra_vida = BarraVida(50, self.vida) self.barra_vida.setParentItem(self) # Se crean los timers de movimiento y ataque self.avanzar = QtCore.QTimer() self.avanzar.timeout.connect(self.acercarse) self.atacar = QtCore.QTimer() self.atacar.timeout.connect(self.ataque) #self.time = time.time() def gen_path(self, lista): while True: for path in lista: yield "Player/{}/{}.png".format(self.nombre, path) def mover(self, evento): # Apretar una tecla de movimiento cancela el movimiento automático self.avanzar.stop() self.atacar.stop() self.target = None #print(abs(self.time - time.time())) #self.time = time.time() text = evento.text() # para no tener que escribirlo mil veces # se calcula un angulo en base a la ultima posicion del mouse x = self.x() + 25 - self.mouse[0] y = self.y() + 25 - self.mouse[1] self.angulo = -math.degrees(math.atan2(x, y)) if self.angulo < 0: self.angulo += 360 self.rotacion(self.angulo) # Cambia la imagen de movimiento self.setPixmap(QPixmap(next(self.img)).scaled(50, 50)) if text == "w": self.setPos( self.x() + self.velocidad / 33.3 * math.sin(math.radians(180 - self.angulo)), self.y() + self.velocidad / 33.3 * math.cos(math.radians(180 - self.angulo))) elif text == "s": self.setPos( self.x() - self.velocidad / 33.3 * math.sin(math.radians(180 - self.angulo)), self.y() - self.velocidad / 33.3 * math.cos(math.radians(180 - self.angulo))) elif text == "d": self.setPos( self.x() + self.velocidad / 33.3 * math.sin(math.radians(90 - self.angulo)), self.y() + self.velocidad / 33.3 * math.cos(math.radians(90 - self.angulo))) elif text == "a": self.setPos( self.x() - self.velocidad / 33.3 * math.sin(math.radians(90 - self.angulo)), self.y() - self.velocidad / 33.3 * math.cos(math.radians(90 - self.angulo))) def stop(self): if self.nombre == "Roberto": self.setPixmap( QPixmap("Player/Roberto/player0.png").scaled(50, 50)) elif self.nombre == "Chau": self.setPixmap(QPixmap("Player/Chau/player_0.png").scaled(50, 50)) else: self.setPixmap(QPixmap("Player/Hernan/peludo0.png").scaled(50, 50)) def rotacion(self, angulo): self.setRotation(angulo) self.barra_vida.setRotation(-angulo) def atacar_enemigo(self, target): self.target = target if self.target not in self.area.collidingItems(): self.avanzar.start(50) else: self.atacar.start(1000 / self.v_atk) def acercarse(self): x = self.x() + 25 - self.target.x() y = self.y() + 25 - self.target.y() self.angulo = -math.degrees(math.atan2(x, y)) if self.angulo < 0: self.angulo += 360 self.rotacion(self.angulo) # Cambia la imagen de movimiento self.setPixmap(QPixmap(next(self.img)).scaled(50, 50)) # Avanza self.setPos( self.x() + 0.05 * self.velocidad * math.sin(math.radians(180 - self.angulo)), self.y() + 0.05 * self.velocidad * math.cos(math.radians(180 - self.angulo))) # Si llega el timer se termina if self.target in self.area.collidingItems(): self.avanzar.stop() # Y ataca self.atacar.start(1000 / self.v_atk) def ataque(self): if self.scene() and self.target in self.scene().items(): self.target.recibir_daño(self.daño) else: self.target = None self.atacar.stop() def recibir_daño(self, daño): self.vida -= daño self.barra_vida.update(self.vida) if self.vida <= 0: self.__scene = self.scene() self.scene().removeItem(self) self.muerto = True self.muertes += 1 self.revivir = QtCore.QTimer() self.revivir.setSingleShot(True) self.revivir.timeout.connect(self.respawn) self.revivir.start(((1.1)**self.muertes) * 10000) def respawn(self): self.vida = self.barra_vida.vida self.barra_vida.update(self.vida) self.__scene.addItem(self) self.setPos(200, 250) def mouseMoveEvent(self, evento): if self.target == None: x = self.x() + 25 - evento.x() y = self.y() + 25 - evento.y() self.angulo = -math.degrees(math.atan2(x, y)) if self.angulo < 0: self.angulo += 360 self.rotacion(self.angulo) self.mouse = (evento.x(), evento.y())
class RDiscRobot(QObject): def __init__(self, r: float, x: float, y: float, color, line_width: float, text=""): self._visible = 1 self._radius = r self._pos = QPointF(x, y) super().__init__() # The supporting rectangle self.rect = QRectF(x - r, y - r, 2 * r, 2 * r) # The underlying QGraphicsEllipseItem self.disc = QGraphicsEllipseItem() self.disc.setRect(self.rect) self.disc.setBrush(QtGui.QBrush(color)) # The underlying QGraphicsTextItem self._text: QGraphicsTextItem = QGraphicsTextItem(text) transform = QTransform.fromScale(0.3, -0.3) self._text.setTransformOriginPoint(self._pos) self._text.setTransform(transform) self._text.setPos(QPointF(x - 1.8, y + 1.8)) font = QFont("Times", 2) self._text.setFont(font) pen = QPen() pen.setWidthF(line_width) self.disc.setPen(pen) self._visible = 1 def x(self) -> float: return self._pos.x() def y(self) -> float: return self._pos.y() def set_text(self, text: str): self._text.setPlainText(text) # The following functions are for animation support @pyqtProperty(QPointF) def pos(self): return self._pos @pos.setter def pos(self, value): self.rect = QRectF(value.x() - self._radius, value.y() - self._radius, 2 * self._radius, 2 * self._radius) self.disc.setRect(self.rect) self._text.setPos(QPointF(value.x() - 1.8, value.y() + 1.8)) self._pos = value @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if value > 0: self.disc.show() self._text.show() else: self.disc.hide() self._text.hide() self._visible = value @pyqtProperty(int) def text(self): if self._text.toPlainText() == '': return 0 return int(self._text.toPlainText()) @text.setter def text(self, val: int): if val == 0: self._text.setPlainText('') else: self._text.setPlainText(str(val))
class SliceNucleicAcidPartItem(QAbstractPartItem): """Parent should be either a SliceRootItem, or an AssemblyItem. Invariant: keys in _empty_helix_hash = range(_nrows) x range(_ncols) where x is the cartesian product. Attributes: active_virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.SliceVirtualHelixItem): Description grab_cornerBR (TYPE): Description grab_cornerTL (TYPE): Description griditem (TYPE): Description outline (TYPE): Description prexover_manager (TYPE): Description scale_factor (TYPE): Description """ _RADIUS = styles.SLICE_HELIX_RADIUS def __init__(self, model_part_instance, viewroot, parent=None): """Summary Args: model_part_instance (TYPE): Description viewroot (TYPE): Description parent (None, optional): Description """ super(SliceNucleicAcidPartItem, self).__init__(model_part_instance, viewroot, parent) self._getActiveTool = viewroot.manager.activeToolGetter m_p = self._model_part self._controller = NucleicAcidPartItemController(self, m_p) self.scale_factor = self._RADIUS / m_p.radius() self.active_virtual_helix_item = None self.prexover_manager = PreXoverManager(self) self.hide() # hide while until after attemptResize() to avoid flicker self._rect = QRectF(0., 0., 1000., 1000.) # set this to a token value self.boundRectToModel() self.setPen(getNoPen()) self.setRect(self._rect) self.setAcceptHoverEvents(True) # Cache of VHs that were active as of last call to activeSliceChanged # If None, all slices will be redrawn and the cache will be filled. # Connect destructor. This is for removing a part from scenes. # initialize the NucleicAcidPartItem with an empty set of old coords self.setZValue(styles.ZPARTITEM) self.outline = outline = QGraphicsRectItem(self) o_rect = self.configureOutline(outline) outline.setFlag(QGraphicsItem.ItemStacksBehindParent) outline.setZValue(styles.ZDESELECTOR) model_color = m_p.getColor() self.outline.setPen(getPenObj(model_color, _DEFAULT_WIDTH)) GC_SIZE = 20 self.grab_cornerTL = GrabCornerItem(GC_SIZE, model_color, True, self) self.grab_cornerTL.setTopLeft(o_rect.topLeft()) self.grab_cornerBR = GrabCornerItem(GC_SIZE, model_color, True, self) self.grab_cornerBR.setBottomRight(o_rect.bottomRight()) self.griditem = GridItem(self, self._model_props['grid_type']) self.griditem.setZValue(1) self.grab_cornerTL.setZValue(2) self.grab_cornerBR.setZValue(2) self.vhi_hint_item = QGraphicsEllipseItem(_DEFAULT_RECT, self) self.vhi_hint_item.setPen(_MOD_PEN) self.vhi_hint_item.setZValue(styles.ZPARTITEM) # select upon creation for part in m_p.document().children(): if part is m_p: part.setSelected(True) else: part.setSelected(False) self.show() # end def ### SIGNALS ### ### SLOTS ### def partActiveVirtualHelixChangedSlot(self, part, id_num): """Summary Args: part (TYPE): Description id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. Args: TYPE: Description """ vhi = self._virtual_helix_item_hash.get(id_num, None) self.setActiveVirtualHelixItem(vhi) self.setPreXoverItemsVisible(vhi) # end def def partActiveBaseInfoSlot(self, part, info): """Summary Args: part (TYPE): Description info (TYPE): Description Args: TYPE: Description """ pxom = self.prexover_manager pxom.deactivateNeighbors() if info and info is not None: id_num, is_fwd, idx, _ = info pxom.activateNeighbors(id_num, is_fwd, idx) # end def def partPropertyChangedSlot(self, model_part, property_key, new_value): """Summary Args: model_part (Part): The model part property_key (TYPE): Description new_value (TYPE): Description Args: TYPE: Description """ if self._model_part == model_part: self._model_props[property_key] = new_value if property_key == 'color': self.outline.setPen(getPenObj(new_value, _DEFAULT_WIDTH)) for vhi in self._virtual_helix_item_hash.values(): vhi.updateAppearance() self.grab_cornerTL.setBrush(getBrushObj(new_value)) self.grab_cornerBR.setBrush(getBrushObj(new_value)) elif property_key == 'is_visible': if new_value: self.show() else: self.hide() elif property_key == 'grid_type': self.griditem.setGridType(new_value) # end def def partRemovedSlot(self, sender): """docstring for partRemovedSlot Args: sender (obj): Model object that emitted the signal. """ self.parentItem().removePartItem(self) scene = self.scene() scene.removeItem(self) self._model_part = None self._mod_circ = None self._controller.disconnectSignals() self._controller = None self.grab_cornerTL = None self.grab_cornerBR = None self.griditem = None # end def def partVirtualHelicesTranslatedSlot(self, sender, vh_set, left_overs, do_deselect): """ left_overs are neighbors that need updating due to changes Args: sender (obj): Model object that emitted the signal. vh_set (TYPE): Description left_overs (TYPE): Description do_deselect (TYPE): Description """ if do_deselect: tool = self._getActiveTool() if tool.methodPrefix() == "selectTool": if tool.isSelectionActive(): # tool.deselectItems() tool.modelClear() # 1. move everything that moved for id_num in vh_set: vhi = self._virtual_helix_item_hash[id_num] vhi.updatePosition() # 2. now redraw what makes sense to be redrawn for id_num in vh_set: vhi = self._virtual_helix_item_hash[id_num] self._refreshVirtualHelixItemGizmos(id_num, vhi) for id_num in left_overs: vhi = self._virtual_helix_item_hash[id_num] self._refreshVirtualHelixItemGizmos(id_num, vhi) # 0. clear PreXovers: # self.prexover_manager.hideGroups() # if self.active_virtual_helix_item is not None: # self.active_virtual_helix_item.deactivate() # self.active_virtual_helix_item = None avhi = self.active_virtual_helix_item self.setPreXoverItemsVisible(avhi) self.enlargeRectToFit() # end def def _refreshVirtualHelixItemGizmos(self, id_num, vhi): """Update props and appearance of self & recent neighbors. Ultimately triggered by a partVirtualHelicesTranslatedSignal. Args: id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. vhi (cadnano.gui.views.sliceview.virtualhelixitem.SliceVirtualHelixItem): the item associated with id_num """ neighbors = vhi.cnModel().getProperty('neighbors') neighbors = literal_eval(neighbors) vhi.beginAddWedgeGizmos() for nvh in neighbors: nvhi = self._virtual_helix_item_hash.get(nvh, False) if nvhi: vhi.setWedgeGizmo(nvh, nvhi) # end for vhi.endAddWedgeGizmos() # end def def partVirtualHelixPropertyChangedSlot(self, sender, id_num, virtual_helix, keys, values): """Summary Args: sender (obj): Model object that emitted the signal. id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. keys (tuple): keys that changed values (tuple): new values for each key that changed Args: TYPE: Description """ if self._model_part == sender: vh_i = self._virtual_helix_item_hash[id_num] vh_i.virtualHelixPropertyChangedSlot(keys, values) # end def def partVirtualHelixAddedSlot(self, sender, id_num, virtual_helix, neighbors): """Summary Args: sender (obj): Model object that emitted the signal. id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. neighbors (TYPE): Description Args: TYPE: Description """ vhi = SliceVirtualHelixItem(virtual_helix, self) self._virtual_helix_item_hash[id_num] = vhi self._refreshVirtualHelixItemGizmos(id_num, vhi) for neighbor_id in neighbors: nvhi = self._virtual_helix_item_hash.get(neighbor_id, False) if nvhi: self._refreshVirtualHelixItemGizmos(neighbor_id, nvhi) self.enlargeRectToFit() # end def def partVirtualHelixRemovingSlot(self, sender, id_num, virtual_helix, neighbors): """Summary Args: sender (obj): Model object that emitted the signal. id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. neighbors (TYPE): Description Args: TYPE: Description """ tm = self._viewroot.manager tm.resetTools() self.removeVirtualHelixItem(id_num) for neighbor_id in neighbors: nvhi = self._virtual_helix_item_hash[neighbor_id] self._refreshVirtualHelixItemGizmos(neighbor_id, nvhi) # end def def partSelectedChangedSlot(self, model_part, is_selected): """Set this Z to front, and return other Zs to default. Args: model_part (Part): The model part is_selected (TYPE): Description """ if is_selected: # self._drag_handle.resetAppearance(_SELECTED_COLOR, _SELECTED_WIDTH, _SELECTED_ALPHA) self.setZValue(styles.ZPARTITEM + 1) else: # self._drag_handle.resetAppearance(self.modelColor(), _DEFAULT_WIDTH, _DEFAULT_ALPHA) self.setZValue(styles.ZPARTITEM) # end def def partVirtualHelicesSelectedSlot(self, sender, vh_set, is_adding): """is_adding (bool): adding (True) virtual helices to a selection or removing (False) Args: sender (obj): Model object that emitted the signal. vh_set (TYPE): Description is_adding (TYPE): Description """ select_tool = self._viewroot.select_tool if is_adding: # print("got the adding slot in path") select_tool.selection_set.update(vh_set) select_tool.setPartItem(self) select_tool.getSelectionBoundingRect() else: select_tool.deselectSet(vh_set) # end def def partDocumentSettingChangedSlot(self, part, key, value): """Summary Args: part (TYPE): Description key (TYPE): Description value (TYPE): Description Args: TYPE: Description Raises: ValueError: Description """ if key == 'grid': print("grid change", value) if value == 'lines and points': self.griditem.setDrawlines(True) elif value == 'points': self.griditem.setDrawlines(False) else: raise ValueError("unknown grid styling") # end def ### ACCESSORS ### def boundingRect(self): """Summary Args: TYPE: Description """ return self._rect # end def def modelColor(self): """Summary Args: TYPE: Description """ return self._model_props['color'] # end def def window(self): """Summary Args: TYPE: Description """ return self.parentItem().window() # end def def setActiveVirtualHelixItem(self, new_active_vhi): """Summary Args: new_active_vhi (TYPE): Description """ current_vhi = self.active_virtual_helix_item # print(current_vhi, new_active_vhi) if new_active_vhi != current_vhi: if current_vhi is not None: current_vhi.deactivate() if new_active_vhi is not None: new_active_vhi.activate() self.active_virtual_helix_item = new_active_vhi # end def def setPreXoverItemsVisible(self, virtual_helix_item): """ self._pre_xover_items list references prexovers parented to other PathHelices such that only the activeHelix maintains the list of visible prexovers Args: virtual_helix_item (cadnano.gui.views.sliceview.virtualhelixitem.SliceVirtualHelixItem): Description """ vhi = virtual_helix_item pxom = self.prexover_manager if vhi is None: pxom.hideGroups() return # print("slice.setPreXoverItemsVisible", virtual_helix_item.idNum()) part = self.part() info = part.active_base_info if info: id_num, is_fwd, idx, to_vh_id_num = info per_neighbor_hits, pairs = part.potentialCrossoverMap(id_num, idx) pxom.activateVirtualHelix(virtual_helix_item, idx, per_neighbor_hits, pairs) # end def def setSelectionFilter(self, filter_name_list): if 'virtual_helix' in filter_name_list: self.vhi_hint_item.setPen(_MOD_PEN) else: self.vhi_hint_item.setPen(_INACTIVE_PEN) def removeVirtualHelixItem(self, id_num): """Summary Args: id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. Args: TYPE: Description """ vhi = self._virtual_helix_item_hash[id_num] if vhi == self.active_virtual_helix_item: self.active_virtual_helix_item = None vhi.virtualHelixRemovedSlot() del self._virtual_helix_item_hash[id_num] # end def def reconfigureRect(self, top_left, bottom_right, padding=_RADIUS): """Summary NOTE: we skip padding Bottom right Y dimension Args: top_left (TYPE): Description bottom_right (TYPE): Description Returns: tuple: tuple of point tuples representing the top_left and bottom_right as reconfigured with padding """ rect = self._rect ptTL = QPointF(*self.padTL(padding, *top_left)) if top_left else rect.topLeft() ptBR = QPointF(*self.padBR(padding, *bottom_right)) if bottom_right else rect.bottomRight() self._rect = new_rect = QRectF(ptTL, ptBR) self.setRect(new_rect) self.configureOutline(self.outline) self.griditem.updateGrid() return (ptTL.x(), ptTL.y()), (ptBR.x(), ptBR.y()) # end def def padTL(self, padding, xTL, yTL): return xTL - padding, yTL - padding # end def def padBR(self, padding, xBR, yBR): return xBR + padding, yBR # end def def enlargeRectToFit(self): """Enlarges Part Rectangle to fit the model bounds. Call this when adding a SliceVirtualHelixItem. Pad """ xTL, yTL, xBR, yBR = self.getModelBounds() tl, br = self.reconfigureRect((xTL, yTL), (xBR, yBR)) self.grab_cornerTL.alignPos(*tl) self.grab_cornerBR.alignPos(*br) # end def ### PRIVATE SUPPORT METHODS ### def configureOutline(self, outline): """Adjusts `outline` size with default padding. Args: outline (TYPE): Description Returns: o_rect (QRect): `outline` rect adjusted by _BOUNDING_RECT_PADDING """ _p = _BOUNDING_RECT_PADDING o_rect = self.rect().adjusted(-_p, -_p, _p, _p) outline.setRect(o_rect) return o_rect # end def def boundRectToModel(self): """update the boundaries to what's in the model with a minimum size """ xTL, yTL, xBR, yBR = self.getModelBounds() self._rect = QRectF(QPointF(xTL, yTL), QPointF(xBR, yBR)) # end def def getModelBounds(self): """Bounds in form of Qt scaled from model Args: Tuple (top_left, bottom_right) """ xLL, yLL, xUR, yUR = self.part().boundDimensions(self.scale_factor) return xLL, -yUR, xUR, -yLL # end def def bounds(self): """x_low, x_high, y_low, y_high """ rect = self._rect return (rect.left(), rect.right(), rect.bottom(), rect.top()) ### PUBLIC SUPPORT METHODS ### def setModifyState(self, bool_val): """Hides the mod_rect when modify state disabled. Args: bool_val (TYPE): what the modifystate should be set to. """ self._can_show_mod_circ = bool_val if bool_val == False: self._mod_circ.hide() # end def def updateStatusBar(self, status_str): """Shows status_str in the MainWindow's status bar. Args: status_str (TYPE): Description """ pass # disabled for now. # self.window().statusBar().showMessage(status_str, timeout) # end def def zoomToFit(self): """Summary Args: TYPE: Description """ thescene = self.scene() theview = thescene.views()[0] theview.zoomToFit() # end def ### EVENT HANDLERS ### def mousePressEvent(self, event): """Handler for user mouse press. Args: event (QGraphicsSceneMouseEvent): Contains item, scene, and screen coordinates of the the event, and previous event. Args: TYPE: Description """ if event.button() == Qt.RightButton: return part = self._model_part part.setSelected(True) if self.isMovable(): return QGraphicsItem.mousePressEvent(self, event) tool = self._getActiveTool() if tool.FILTER_NAME not in part.document().filter_set: return tool_method_name = tool.methodPrefix() + "MousePress" if hasattr(self, tool_method_name): getattr(self, tool_method_name)(tool, event) else: event.setAccepted(False) QGraphicsItem.mousePressEvent(self, event) # end def def hoverEnterEvent(self, event): self.vhi_hint_item.show() #print("Slice VHI hoverEnterEvent") # def hoverMoveEvent(self, event): # print("Slice VHI hoverMoveEvent") def hoverLeaveEvent(self, event): self.vhi_hint_item.hide() #print("Slice VHI hoverLeaveEvent") def hoverMoveEvent(self, event): """Summary Args: event (TYPE): Description Args: TYPE: Description """ # print("Slice VHI hoverMoveEvent") self.vhi_hint_item.setPos(event.pos()-QPointF(_RADIUS-DELTA, _RADIUS-DELTA)) tool = self._getActiveTool() tool_method_name = tool.methodPrefix() + "HoverMove" if hasattr(self, tool_method_name): getattr(self, tool_method_name)(tool, event) else: event.setAccepted(False) QGraphicsItem.hoverMoveEvent(self, event) # end def def getModelPos(self, pos): """Y-axis is inverted in Qt +y === DOWN Args: pos (TYPE): Description """ sf = self.scale_factor x, y = pos.x()/sf, -1.0*pos.y()/sf return x, y # end def def getVirtualHelixItem(self, id_num): """Summary Args: id_num (int): VirtualHelix ID number. See `NucleicAcidPart` for description and related methods. Returns: TYPE: Description """ return self._virtual_helix_item_hash.get(id_num) # end def def createToolMousePress(self, tool, event, alt_event=None): """Summary Args: tool (TYPE): Description event (TYPE): Description alt_event (None, optional): Description Returns: TYPE: Description """ # 1. get point in model coordinates: part = self._model_part if alt_event is None: # print() pt = tool.eventToPosition(self, event) # print("reg_event", pt) else: # pt = alt_event.scenePos() # pt = self.mapFromScene(pt) pt = alt_event.pos() # print("alt_event", pt) if pt is None: tool.deactivate() return QGraphicsItem.mousePressEvent(self, event) part_pt_tuple = self.getModelPos(pt) mod = Qt.MetaModifier if not (event.modifiers() & mod): pass # don't create a new VirtualHelix if the click overlaps with existing # VirtualHelix current_id_num = tool.idNum() check = part.isVirtualHelixNearPoint(part_pt_tuple, current_id_num) # print("current_id_num", current_id_num, check) # print(part_pt_tuple) tool.setPartItem(self) if check: id_num = part.getVirtualHelixAtPoint(part_pt_tuple) # print("got a check", id_num) if id_num is not None: # print("restart", id_num) vhi = self._virtual_helix_item_hash[id_num] tool.setVirtualHelixItem(vhi) tool.startCreation() else: # print("creating", part_pt_tuple) part.createVirtualHelix(*part_pt_tuple) id_num = part.getVirtualHelixAtPoint(part_pt_tuple) vhi = self._virtual_helix_item_hash[id_num] tool.setVirtualHelixItem(vhi) tool.startCreation() # end def def createToolHoverMove(self, tool, event): """Summary Args: tool (TYPE): Description event (TYPE): Description Returns: TYPE: Description """ tool.hoverMoveEvent(self, event) return QGraphicsItem.hoverMoveEvent(self, event) # end def def selectToolMousePress(self, tool, event): """ Args: tool (TYPE): Description event (TYPE): Description """ tool.setPartItem(self) pt = tool.eventToPosition(self, event) part_pt_tuple = self.getModelPos(pt) part = self._model_part if part.isVirtualHelixNearPoint(part_pt_tuple): id_num = part.getVirtualHelixAtPoint(part_pt_tuple) if id_num is not None: print(id_num) loc = part.getCoordinate(id_num, 0) print("VirtualHelix #{} at ({:.3f}, {:.3f})".format(id_num, loc[0], loc[1])) else: # tool.deselectItems() tool.modelClear() else: # tool.deselectItems() tool.modelClear() return QGraphicsItem.mousePressEvent(self, event)
class QComposer(object): def __init__(self, tab, tabWd, collectionSelector, actor, proxyActor): self.proxyActor = proxyActor self.actorsItemsChanged = False self.scene = QGraphicsScene() self.scene.selectionChanged.connect(self.gItemSelectionChanged) self.selectionIndicator = QGraphicsEllipseItem(-100, -100, INDICATOR_SIZE, INDICATOR_SIZE) self.scene.addItem(self.selectionIndicator) TreeItem.scene = self.scene TreeItem.changeAmountCallback = self.changeAmount TreeItem.uiFactory = ComposerItemUIFactory(self.scene) self.collectionSelector = collectionSelector collectionSelector.selectedChanged.connect(self.aItemSelectionChanged) self.targetActor = actor actor.ownedItems.aItemChanged.connect(self.targetActorsItemChanged) self.graphicsViewWd = tabWd.findChild(QGraphicsView, 'composer_view') self.graphicsViewWd.setScene(self.scene) self.tab = tab tab.tabBecameVisible.connect(self.tabBecameVisible) tabWd.findChild(QPushButton, "make_bt").clicked.connect(self.makeClicked) tabWd.findChild(QPushButton, "autofill_bt").clicked.connect(self.autofillClicked) tabWd.findChild(QPushButton, "clear_bt").clicked.connect(self.clearClicked) self.gItemSelectionChanged() self.rootGItem = TreeItem() self.rootGItem.drawCached() def tabBecameVisible(self): if self.actorsItemsChanged: self.updateProxyActorsItems() def targetActorsItemChanged(self, batch): if not self.tab.isActiveTab: self.actorsItemsChanged = True else: self.updateProxyActorsItems() def updateProxyActorsItems(self): # copy inplace copyInPlace(self.targetActor.ownedItems, self.proxyActor.ownedItems) self.proxyActor.ownedItems.startBatch() self.applyItemUpdate(self.rootGItem) self.rootGItem.drawCached() self.proxyActor.ownedItems.clearBatch() def applyItemUpdate(self, gItem): if gItem.aItem == None: return i = self.proxyActor.ownedItems.getAItemById(gItem.aItem.item.id) if i == None: gItem.clear(None, TreeItem.CLEAR_IGNORE) return gItem.takePreferredAmountOfItem(self.proxyActor, i) for c in gItem.children: self.applyItemUpdate(c) def gItemSelectionChanged(self): gItems = self.scene.selectedItems() if len(gItems) == 0: self.collectionSelector.setConstantPreFilter(ConstFilter(False)) self.selectionIndicator.hide() else: gItem = gItems[0] self.selectionIndicator.show() self.selectionIndicator.setPos(gItem.x + INDICATOR_OFFSET, gItem.y + INDICATOR_OFFSET) if gItem.aItem != None: m = self.collectionSelector.tableModel for i in range(len(m.filteredAItemList)): if gItem.aItem == m.filteredAItemList[i]: # self.collectionSelector.tableView.setCurrentIndex(self.collectionSelector.proxyModel.mapFromSource(m.index(i, 0))) self.collectionSelector.tableView.selectionModel( ).select( self.collectionSelector.proxyModel.mapFromSource( m.index(i, 0)), QItemSelectionModel.SelectCurrent) break self.collectionSelector.setConstantPreFilter( self.getFilterForGItem(gItem)) def aItemSelectionChanged(self, aItem): if aItem == None or not aItem.isValid(): return gItems = self.scene.selectedItems() if len(gItems) == 0: return else: gItem = gItems[0] gItem.setAItem(aItem, self.proxyActor) def getFilterForGItem(self, gItem): if gItem.parent == None: return RootFilter(gItem.aItem) else: return ItemFuncFilter(gItem) def changeAmount(self, gItem, amount): if amount < 1: return amount -= gItem.aItem.amount if amount == 0: return if amount < 0: if gItem.aItem.item.amount + amount < 0: amount = -(gItem.aItem.item.amount - 1) self.proxyActor.ownedItems.append( AmountItem(gItem.aItem.item, amount * -1)) gItem.aItem.amount += amount else: i = self.proxyActor.ownedItems.getAItemById(gItem.aItem.item.id) if amount > i.amount: amount = i.amount self.proxyActor.ownedItems.remove( AmountItem(gItem.aItem.item, amount)) gItem.aItem.amount += amount def makeClicked(self): if not self.rootGItem.isValidComposition(): return self.targetActor.ownedItems.startBatch() result = AmountList() self.rootGItem.createComposition(result) self.targetActor.ownedItems.addAmountList(result) aItem = self.rootGItem.aItem self.rootGItem.clear(self.targetActor, TreeItem.CLEAR_REMOVE) self.targetActor.ownedItems.endBatch() i = self.proxyActor.ownedItems.getAItemById(aItem.item.id) self.rootGItem.setAItem(i, self.proxyActor) self.rootGItem.drawCached() def clearClicked(self): self.targetActor.ownedItems.startBatch() self.rootGItem.clear(self.proxyActor, TreeItem.CLEAR_RECYCLE) self.targetActor.ownedItems.endBatch() self.rootGItem.drawCached() def autofillClicked(self): if self.rootGItem.aItem is None: return self.autofill(self.rootGItem) self.rootGItem.drawCached() def autofill(self, gItem): if gItem.aItem == None and (gItem.parent == None or gItem.parent.aItem.item.allowAutoFill): filter = self.getFilterForGItem(gItem) vI = next( (x for x in self.proxyActor.ownedItems if filter.isValidItem(x)), None) if vI == None: return gItem.setAItem(vI, self.proxyActor) for c in gItem.children: self.autofill(c)
class topoGraphScene(QGraphicsScene): ''' docstring: 场景模型类 ''' def __init__(self, parent=None): super().__init__(parent) self.tmpnode = None self.tmpnode_transparent = None self.tmpedge = None self.signal_to_mainwindow = None # 添加特殊节点用于收包动画显示 # TODO: 建立新的点类 self.node_file = QGraphicsEllipseItem(-12, -12, 24, 24) self.node_file_img = QGraphicsPixmapItem( QPixmap(':/file/document').scaled(80, 72), self.node_file) self.node_file_img.setOffset(-40, -90) self.node_file.setPen(QPen(QColor('#ffff80'), 2)) self.node_file.setBrush(Qt.red) self.node_file.setZValue(20) self.addItem(self.node_file) self.node_file.hide() # 设置特殊文本类显示信息 self.baseinfo = Text("信息显示框", font=QFont("SimHei", 12, QFont.Normal), color=QColor("#ff8000")) self.addItem(self.baseinfo) self.baseinfo.setFlag(self.baseinfo.ItemIgnoresTransformations) self.baseinfo.hide() # 观察节点特殊记录 self.node_me = None self.nid_me = None self.waitlist = [] # 用于暂存添加到topo图中的新节点修改属性 # 绘制背景线 self.backgroundLines = [] for i in range(-250, 250): j = i + 250 n1 = Node(nodetype=1) n2 = Node(nodetype=1) n1.setPos(i * 20, -5000) n2.setPos(i * 20, 5000) e = Edge(n1, n2, 2) e.setFlag(e.ItemIsSelectable, False) if j % 8 == 0: e.setPen(QPen(QColor("#111111"), 2, Qt.DotLine)) self.addItem(e) self.backgroundLines.append(e) n3 = Node(nodetype=1) n4 = Node(nodetype=1) n3.setPos(-5000, i * 20) n4.setPos(5000, i * 20) e = Edge(n3, n4, 2) e.setFlag(e.ItemIsSelectable, False) if j % 8 == 0: e.setPen(QPen(QColor("#111111"), 2, Qt.DotLine)) self.addItem(e) self.backgroundLines.append(e) # 拓扑图主要参数 self.ASinfo = {} # AS所含节点信息,格式:id:[node,...] self.belongAS = {} # 节点所属AS,格式:id:ASitem self.nextedges = {} # 边表,邻接表存储,格式:id:[(nextnode, edge),...] self.R = 0 # 布局中大圆半径 # json格式转换时临时保存参数 self.topo = {} self.data = {} def addEdge(self, n1, n2, edge): ''' docstring: 向nextedges字典中添加点-边映射(无向边) ''' tmplist = self.nextedges.get(n1.id, []) tmplist.append((n2, edge)) self.nextedges[n1.id] = tmplist tmplist = self.nextedges.get(n2.id, []) tmplist.append((n1, edge)) self.nextedges[n2.id] = tmplist # 绑定对应点,便于边操作 edge.node1 = n1 edge.node2 = n2 def delEdge(self, n1, n2, edge): ''' docstring: 与上面操作相反 ''' self.nextedges[n1.id].remove((n2, edge)) self.nextedges[n2.id].remove((n1, edge)) def findPath(self, dest): ''' docstring: 选择最短路径,以观察节点为起点,返回到目标节点的路径(nid序列) ''' if self.node_me == None: return None # print('init') for item in self.items(): if isinstance(item, Node): item.setSelected(False) item.isvisited = False item.prenode = None # print('start') tmpqueue = Queue() tmpqueue.put(self.node_me) self.node_me.isvisited = True while not tmpqueue.empty(): topnode = tmpqueue.get() for nextnode, nextedge in self.nextedges[topnode.id]: if not nextnode.isvisited: tmpqueue.put(nextnode) nextnode.isvisited = True nextnode.prenode = topnode # print('get ans') if not dest.isvisited: return None else: ret = [] now = dest while now: ret.append(now.id) now.setSelected(True) now = now.prenode ret.reverse() return ret def resetPos(self): # 设置图元位置 num1 = len(self.ASinfo) self.R = 32 * num1 + 256 now = 0 for nodelist in self.ASinfo.values(): alpha = pi * 2 / num1 * now now += 1 X, Y = (-sin(alpha) * self.R, -cos(alpha) * self.R) # print(i, ':', alpha, '(', X, Y, ')', item.nid) asitem = nodelist.pop() asitem.setPos(X, Y) num2 = len(nodelist) r = num2 * 16 + 100 for i, node in enumerate(nodelist): beta = pi * 2 / num2 * i + alpha x = X - sin(beta) * r y = Y - cos(beta) * r node.setPos(x, y) nodelist.append(asitem) def initTopo(self, path): ''' docstring: 初始化拓扑为配置文件信息 ''' with open(path, 'r') as f: self.data = load(f) self.topo = self.data.get('topo map', {}) if len(self.topo) == 0: return # 添加节点 tmpass = [] tmpnodes = [] for i in range(len(self.topo['nodes'])): node = self.topo['nodes'][i] ntp = node['type'] nnm = node.get('name', None) nsz = node.get('size', 0) nid = node.get('nid', None) font = node.get('font', None) if font: nft = QFont() nft.fromString(font) else: nft = None ncr = node.get('color', None) tfont = node.get('tfont', None) if tfont: tmp = QFont() tmp.fromString(tfont) tfont = tmp tcolor = node.get('tcolor', None) item = Node(ntp, nnm, nsz, nid, nft, ncr, tfont, tcolor) self.addItem(item) pos = node['pos'] item.setPos(pos[0], pos[1]) if ntp == 0: tmpass.append((i, item)) elif ntp == 5: self.node_me = item tmpnodes.append(item) # 添加AS信息 self.ASinfo = {} self.belongAS = {} for (i, asitem) in tmpass: nodelist = [tmpnodes[x] for x in self.topo['ASinfo'][str(i)]] nodelist.append(asitem) self.ASinfo[asitem.id] = nodelist self.belongAS[asitem.id] = asitem for x in self.topo['ASinfo'][str(i)]: self.belongAS[tmpnodes[x].id] = asitem # 添加边 for item in self.topo['edges']: x = item[0] y = item[1] if len(item) > 2: font = None if len(item) > 3: font = QFont() font.fromString(item[3]) color = None if len(item) > 4: color = item[4] edgeitem = Edge(tmpnodes[x], tmpnodes[y], 0, item[2], font, color) else: edgeitem = Edge(tmpnodes[x], tmpnodes[y], linetype=1) self.addItem(edgeitem) self.addEdge(tmpnodes[x], tmpnodes[y], edgeitem) # 根据标志位显示标签 if self.parent().labelenable: for item in self.items(): if isinstance(item, Node) or isinstance(item, Edge): item.label.show() if self.parent().throughputenable: for item in self.items(): if isinstance(item, Node) and not item.myType: if item.id == self.belongAS[self.node_me.id].id: continue item.throughputlabel.show() def saveTopo(self, path): ''' docstring: 存储拓扑图 ''' with open(path, 'r') as f: self.data = load(f) self.topo = {} # 删除所有还在waitlist中的点,视为添加失败 for item in self.waitlist: self.removeItem(item) # 删除所有背景线 for item in self.backgroundLines: self.removeItem(item) # 添加点 tmpnodes = [] tmpnodemap = {} num = 0 for item in self.items(): if isinstance(item, Node): node = {} node['type'] = item.myType if item.myType == 0: node['size'] = item.size node['tfont'] = item.throughputlabel.currentfont.toString() node['tcolor'] = item.throughputlabel.currentcolor.name() elif item.myType == 1 or item.myType == 2 or item.myType == 5: node['nid'] = item.nid node['pos'] = [item.scenePos().x(), item.scenePos().y()] node['name'] = item.name node['font'] = item.label.currentfont.toString() node['color'] = item.label.currentcolor.name() tmpnodes.append(node) tmpnodemap[item.id] = num num += 1 self.topo['nodes'] = tmpnodes # 添加边 tmpedges = [] for item in self.items(): if isinstance(item, Edge): x = tmpnodemap[item.node1.id] y = tmpnodemap[item.node2.id] if item.PX: font = item.label.currentfont.toString() color = item.label.currentcolor.name() tmpedges.append([x, y, item.PX, font, color]) else: tmpedges.append([x, y]) self.topo['edges'] = tmpedges # 添加AS信息 tmpasinfo = {} for asid, nodes in self.ASinfo.items(): x = tmpnodemap[asid] y = [] for node in nodes: z = tmpnodemap[node.id] if x != z: y.append(z) tmpasinfo[str(x)] = y self.topo['ASinfo'] = tmpasinfo with open(path, 'w') as f: self.data['topo map'] = self.topo dump(self.data, f)