def initMap(self): features = json.load(open("Data/world.json", encoding="utf8")).get("features") for feature in features: geometry = feature.get("geometry") if not geometry: continue _type = geometry.get("type") coordinates = geometry.get("coordinates") for coordinate in coordinates: if _type == "Polygon": polygon = QPolygonF([ QPointF(latitude, -longitude) for latitude, longitude in coordinate ]) item = QGraphicsPolygonItem(polygon) item.setPen(QPen(self.borderColor, 0)) item.setBrush(QBrush(self.backgroundColor)) item.setPos(0, 0) self._scene.addItem(item) elif _type == "MultiPolygon": for _coordinate in coordinate: polygon = QPolygonF([ QPointF(latitude, -longitude) for latitude, longitude in _coordinate ]) item = QGraphicsPolygonItem(polygon) item.setPen(QPen(self.borderColor, 0)) item.setBrush(QBrush(self.backgroundColor)) item.setPos(0, 0) self._scene.addItem(item)
def create_arrow(tile): item = QGraphicsPolygonItem(qt_drawings.arrow_polygon) item.setPen(qt_drawings.no_pen) if tile: item.setBrush(qt_drawings.cyan_brush if tile.is_frozen else qt_drawings.black_brush) item.setTransformOriginPoint(qt_drawings.tile_size / 2, qt_drawings.tile_size / 2) angle = base_scene_reactor.tile_rotation_angles[tile.is_horizontal][tile.is_positive] item.setRotation(angle) else: item.setBrush(qt_drawings.black_brush) return item
class RPolygon(QObject): def __init__(self, points, color, line_width): self._points = [QPointF(p[0], p[1]) for p in points] self._pos = self._points[0] super().__init__() # The underlying QGraphicsPolygonItem self.polygon = QGraphicsPolygonItem() self.polygon.setPolygon(QPolygonF(self._points)) self.polygon.setBrush(QtGui.QBrush(color)) pen = QPen() pen.setWidthF(line_width) self.polygon.setPen(pen) self._visible = 1 def x(self): return self._pos.x() def y(self): return self._pos.y() def points(self): return self._points # The following functions are for animation support @pyqtProperty(QPointF) def pos(self): return self._pos @pos.setter def pos(self, value): delta_x = value.x() - self._pos.x() delta_y = value.y() - self._pos.y() self._points = [ QPointF(p.x() + delta_x, p.y() + delta_y) for p in self._points ] self.polygon.setPolygon(QPolygonF(self._points)) self._pos = value @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if (value > 0): self.polygon.show() else: self.polygon.hide() self._visible = value
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 draw_polygons(self): sf = shapefile.Reader(self.shapefile) polygons = sf.shapes() for polygon in polygons: # convert shapefile geometries into shapely geometries # to extract the polygons of a multipolygon polygon = shapely.geometry.shape(polygon) # if it is a polygon, we use a list to make it iterable if polygon.geom_type == 'Polygon': polygon = [polygon] for land in polygon: qt_polygon = QPolygonF() for lon, lat in land.exterior.coords: px, py = self.to_canvas_coordinates(lon, lat) if px > 1e+10: continue qt_polygon.append(QPointF(px, py)) polygon_item = QGraphicsPolygonItem(qt_polygon) polygon_item.setBrush(self.land_brush) polygon_item.setPen(self.land_pen) polygon_item.setZValue(1) yield polygon_item
def create_element_model(self, gscene): clut = rgbt.RGBTable(filename='gui/red_blue64.csv', data_range=[10.0, 100.]) ele_model = ele.ElementModel() ele_model.elements_from_sequence(self.seq_model) pen = QPen() pen.setCosmetic(True) for e in ele_model.elements: poly = e.shape() polygon = QPolygonF() for p in poly: polygon.append(QPointF(p[0], p[1])) gpoly = QGraphicsPolygonItem() gpoly.setPolygon(polygon) # set element color based on V-number gc = float(e.medium.glass_code()) vnbr = round(100.0 * (gc - int(gc)), 3) ergb = clut.get_color(vnbr) gpoly.setBrush(QColor(*ergb)) gpoly.setPen(pen) t = e.tfrm[1] gpoly.setPos(QPointF(t[2], -t[1])) gscene.addItem(gpoly)
def create_ray_model(self, gscene, start_surf=1): tfrms = self.seq_model.compute_global_coords(start_surf) rayset = self.seq_model.trace_boundary_rays() start_offset = 0.1 * gscene.sceneRect().width() if abs(tfrms[0][1][2]) > start_offset: tfrms[0] = self.seq_model.shift_start_of_rayset( rayset, start_offset) pen = QPen() pen.setCosmetic(True) for rays in rayset: poly1 = [] for i, r in enumerate(rays[3][0][0:]): rot, trns = tfrms[i] p = rot.dot(r[0]) + trns # print(i, r[0], rot, trns, p) poly1.append(QPointF(p[2], -p[1])) poly2 = [] for i, r in enumerate(rays[4][0][0:]): rot, trns = tfrms[i] p = rot.dot(r[0]) + trns # print(i, r[0], rot, trns, p) poly2.append(QPointF(p[2], -p[1])) poly2.reverse() poly1.extend(poly2) polygon = QPolygonF() for p in poly1: polygon.append(p) gpoly = QGraphicsPolygonItem() gpoly.setPolygon(polygon) gpoly.setBrush(QColor(254, 197, 254, 64)) # magenta, 25% gpoly.setPen(pen) gscene.addItem(gpoly)
def draw_polygons(self): sf = shapefile.Reader(self.shapefile) polygons = sf.shapes() for polygon in polygons: # convert shapefile geometries into shapely geometries # to extract the polygons of a multipolygon polygon = shapely.geometry.shape(polygon) # if it is a polygon, we use a list to make it iterable if polygon.geom_type == 'Polygon': polygon = [polygon] for land in polygon: qt_polygon = QPolygonF() land = str(land)[10:-2].replace(', ', ',').replace(' ', ',') coords = land.replace('(', '').replace(')', '').split(',') for lon, lat in zip(coords[0::2], coords[1::2]): px, py = self.to_canvas_coordinates(lon, lat) if px > 1e+10: continue qt_polygon.append(QPointF(px, py)) polygon_item = QGraphicsPolygonItem(qt_polygon) polygon_item.setBrush(self.land_brush) polygon_item.setPen(self.land_pen) polygon_item.setZValue(1) yield polygon_item
class Tower(StaticGameObject): def __init__(self): super().__init__() # initialize attack range (area) self.attack_area = QGraphicsPolygonItem() self.attack_dest = QPointF(0, 0) self.has_target = False # set the graphics self.setPixmap( QPixmap('./res/imgs/lol_tower.png').scaled(80, 80, Qt.KeepAspectRatio)) # initializes health h = Bar(self) h.set_max_val(250) h.set_current_val(250) self.set_health(h) self.attack = 30 # create points vector points = [ QPointF(1, 0), QPointF(2, 0), QPointF(3, 1), QPointF(3, 2), QPointF(2, 3), QPointF(1, 3), QPointF(0, 2), QPointF(0, 1) ] # scale points SCALE_FACTOR = 100 points = [p * SCALE_FACTOR for p in points] self.range = SCALE_FACTOR # create polygon self.polygon = QPolygonF(points) # create QGraphicsPolygonItem self.attack_area = QGraphicsPolygonItem(self.polygon, self) self.attack_area.setPen(QPen(Qt.DotLine)) # move the polygon poly_center = QPointF(1.5 * SCALE_FACTOR, 1.5 * SCALE_FACTOR) poly_center = self.mapToScene(poly_center) tower_center = QPointF(self.x() + 40, self.y() + 40) ln = QLineF(poly_center, tower_center) self.attack_area.setPos(self.x() + ln.dx(), self.y() + ln.dy()) # connect a timer to acquire target self.damage_timer = QTimer() self.damage_timer.timeout.connect(self.acquire_target) self.damage_timer.start(1000) # allow responding to hover events self.setAcceptHoverEvents(True) def fire(self): if not self.scene(): return bullet = Bullet(self) bullet.setPos(self.x() + 40, self.y() + 40) # set the angle to be paralel to the line that connects the # tower and target ln = QLineF(QPointF(self.x() + 40, self.y() + 40), self.attack_dest) angle = -1 * ln.angle() # -1 to make it clock wise bullet.setRotation(angle) self.scene().addItem(bullet) def acquire_target(self): # get a list of all items colliding with attack area colliding_items = self.attack_area.collidingItems() self.has_target = False closest_dist = 300 closest_point = QPointF(0, 0) for i in colliding_items: if hasattr(i, 'team') and i.team != self.team: this_distance = self.distance_to(i) if this_distance < closest_dist: closest_dist = this_distance closest_point = i.pos() self.has_target = True self.attack_dest = closest_point if self.has_target: self.fire() def distance_to(self, item): '''item: QGraphicsItem ''' ln = QLineF(self.pos(), item.pos()) return ln.length() def hoverEnterEvent(self, event): '''event: QGraphicsSceneHoverEvent ''' # change pixmap if self.team == 2: self.setPixmap( QPixmap('./res/imgs/lol_tower_red.png').scaled( 80, 80, Qt.KeepAspectRatio)) def hoverLeaveEvent(self, event): '''event: QGraphicsSceneHoverEvent ''' # change back pixmap if self.team == 2: self.setPixmap( QPixmap('./res/imgs/lol_tower.png').scaled( 80, 80, Qt.KeepAspectRatio))
class GraphicLine(QGraphicsLineItem): """ This class is a graphic line with an arrow which connects two blocks in the scene. Attributes ---------- origin : QGraphicsRectItem Origin rect of the line. destination : QGraphicsRectItem Destination rect of the line. scene : QGraphicsScene Current drawing scene. brush : QBrush Brush to draw the arrow. pen : QPen Pen to draw the arrow. arrow_head : QGraphicsPolygonItem Final arrow of the line. arrow_size : int Size of the head of the arrow. dim_label : QGraphicsTextItem Text showing the dimensions of the edge. is_valid : bool Flag monitoring whether the connection is consistent. Methods ---------- gen_endpoints(QRectF, QRectF) Returns the shortest connection between the two rects. draw_arrow() Draws the polygon for the arrow. set_valid(bool) Assign validity for this line. update_dims(tuple) Update the line dimensions. update_pos(QRectF) Update the line position given the new rect position. remove_self() Delete this line. """ def __init__(self, origin: QGraphicsRectItem, destination: QGraphicsRectItem, scene): super(GraphicLine, self).__init__() self.origin = origin self.destination = destination self.scene = scene # This flag confirms a legal connection self.is_valid = True # Get the four sides of the rects destination_lines = u.get_sides_of( self.destination.sceneBoundingRect()) origin_lines = u.get_sides_of(self.origin.sceneBoundingRect()) # Get the shortest edge between the two blocks self.setLine(self.gen_endpoints(origin_lines, destination_lines)) self.brush = QBrush(QColor(style.GREY_0)) self.pen = QPen(QColor(style.GREY_0)) self.pen.setWidth(4) self.pen.setCapStyle(Qt.RoundCap) self.pen.setJoinStyle(Qt.RoundJoin) self.setPen(self.pen) # Dimensions labels self.dim_label = QGraphicsTextItem() self.dim_label.setZValue(6) self.scene.addItem(self.dim_label) # Arrow head self.arrow_head = QGraphicsPolygonItem() self.arrow_head.setPen(self.pen) self.arrow_head.setBrush(self.brush) self.arrow_size = 15.0 self.draw_arrow() @staticmethod def gen_endpoints(origin_sides: dict, destination_sides: dict) -> QLineF: """ This method finds the shortest path between two rectangles. Parameters ---------- origin_sides : dict The dictionary {side_label: side_size} of the starting rect. destination_sides : dict The dictionary {side_label: side_size} of the ending rect. Returns ---------- QLineF The shortest line. """ # Init the line with the maximum possible value shortest_line = QLineF(-sys.maxsize / 2, -sys.maxsize / 2, sys.maxsize / 2, sys.maxsize / 2) for o_side, origin_side in origin_sides.items(): o_mid_x, o_mid_y = u.get_midpoint(o_side, origin_side) for d_side, destination_side in destination_sides.items(): d_mid_x, d_mid_y = u.get_midpoint(d_side, destination_side) # Update line line = QLineF(o_mid_x, o_mid_y, d_mid_x, d_mid_y) if line.length() < shortest_line.length(): shortest_line = line return shortest_line def draw_arrow(self) -> None: """ This method draws an arrow at the end of the line. """ polygon_arrow_head = QPolygonF() # Compute the arrow angle angle = math.acos(self.line().dx() / self.line().length()) angle = ((math.pi * 2) - angle) # Compute the direction where the arrow points (1 up, -1 down) arrow_direction = 1 if math.asin(self.line().dy() / self.line().length()) < 0: arrow_direction = -1 # First point of the arrow tail arrow_p1 = self.line().p2() - arrow_direction * QPointF( arrow_direction * math.sin(angle + math.pi / 2.5) * self.arrow_size, math.cos(angle + math.pi / 2.5) * self.arrow_size) # Second point of the arrow tail arrow_p2 = self.line().p2() - arrow_direction * QPointF( arrow_direction * math.sin(angle + math.pi - math.pi / 2.5) * self.arrow_size, math.cos(angle + math.pi - math.pi / 2.5) * self.arrow_size) # Third point is the line end polygon_arrow_head.append(self.line().p2()) polygon_arrow_head.append(arrow_p2) polygon_arrow_head.append(arrow_p1) # Add the arrow to the scene self.arrow_head.setZValue(1) self.arrow_head.setParentItem(self) self.arrow_head.setPolygon(polygon_arrow_head) def set_valid(self, valid: bool) -> None: """ This method changes the arrow style: if the connection is not valid the arrow becomes red, otherwise it remains grey with dimensions displayed. Parameters ---------- valid : bool New value for the legality flag. """ if valid: self.is_valid = True self.pen.setColor(QColor(style.GREY_0)) self.brush.setColor(QColor(style.GREY_0)) self.dim_label.setVisible(False) else: self.is_valid = False self.pen.setColor(QColor(style.RED_2)) self.brush.setColor(QColor(style.RED_2)) if self.scene.is_dim_visible: self.dim_label.setVisible(True) def update_dims(self, dims: tuple) -> None: """ This method updates the input & output dimensions. Parameters ---------- dims : tuple The new dimensions to update. """ self.dim_label.setHtml("<div style = 'background-color: " + style.RED_2 + "; color: white; font-family: consolas;'>" + str(dims) + "</div>") self.dim_label.setPos(self.line().center()) def update_pos(self, new_target: QRectF): """ This method updates the line as it origin or its destination has changed location. Parameters ---------- new_target : QRectF """ if new_target == self.destination: self.destination = new_target elif new_target == self.origin: self.origin = new_target # Get the four sides of the rects destination_lines = u.get_sides_of( self.destination.sceneBoundingRect()) origin_lines = u.get_sides_of(self.origin.sceneBoundingRect()) # Get the shortest edge between the two blocks self.setLine(self.gen_endpoints(origin_lines, destination_lines)) self.draw_arrow() self.dim_label.setPos(self.line().center()) def remove_self(self) -> None: """ The line is removed from the scene along with origin and destination pointers. """ self.scene.removeItem(self) self.scene.edges.remove(self) self.scene.removeItem(self.dim_label) self.origin = None self.destination = None
class RelationItem(): ''' This represents one relationship on the diagram. This class creates the arc graphics item and the text graphics item and draws them on the scene. The RelationInstance class manages reading and writing the relationship to Neo4j ''' def __init__(self, scene, relationInstance=None): self.scene = scene self.model = self.scene.parent.model self.diagramType = "Instance Relationship" self.logMsg = None # self.relationInstance should have been called self.itemInstance to be consistent # with NodeItem. so we'll set it to none and someday fix this. self.itemInstance = None self.relationInstance = relationInstance self.startNZID = self.relationInstance.startNZID self.endNZID = self.relationInstance.endNZID # get the NodeItem objects for the start and end nodes self.startNode = self.scene.parent.itemDict[self.startNZID] self.endNode = self.scene.parent.itemDict[self.endNZID] self.numRels = self.scene.parent.numRels(self.relationInstance.startNZID, self.relationInstance.endNZID) # print("numRels:{}".format(self.numRels)) self.lineType = None # print("num rels{}".format(str(self.numRels))) # initialize the two qgraphicsitems needed to draw a relationship to None self.IRel = None self.IRtext = None self.bunnyEar = None self.endDot = None self.arrowHead = None self.TAline1 = None self.TAline2 = None self.debugTriangle = None self.drawRelationship() def name(self, ): return self.relationInstance.NZID def NZID(self, ): return self.relationInstance.NZID def getFormat(self, ): ''' determine if the rel instance has a template format or should use the project default format ''' # get the default self.relFormat = IRelFormat(formatDict=self.model.modelData["IRformat"]) # get a custom template format if there is one if not self.relationInstance.relTemplate is None: index, relTemplateDict = self.model.getDictByName(topLevel="Relationship Template",objectName=self.relationInstance.relTemplate) if not relTemplateDict is None: self.instanceRelFormatDict = relTemplateDict.get("IRformat", None) if not self.instanceRelFormatDict is None: self.relFormat = IRelFormat(formatDict=self.instanceRelFormatDict) def getNodeId(self): '''return the rel id for the relationship from the IREL or bunnyear graphic item - which ever it is ''' if not self.IRel is None: nodeID = self.IRel.data(NODEID) elif not self.bunnyEar is None: nodeID = self.bunnyEar.data(NODEID) else: nodeID = None return nodeID def clearItem(self, ): if (not self.IRel is None and not self.IRel.scene() is None): self.IRel.scene().removeItem(self.IRel) if (not self.IRtext is None and not self.IRtext.scene() is None): self.IRtext.scene().removeItem(self.IRtext) if (not self.bunnyEar is None and not self.bunnyEar.scene() is None): self.bunnyEar.scene().removeItem(self.bunnyEar) if (not self.endDot is None and not self.endDot.scene() is None): self.endDot.scene().removeItem(self.endDot) if (not self.arrowHead is None and not self.arrowHead.scene() is None): self.arrowHead.scene().removeItem(self.arrowHead) if (not self.TAline2 is None and not self.TAline2.scene() is None): self.TAline2.scene().removeItem(self.TAline2) if (not self.TAline1 is None and not self.TAline1.scene() is None): self.TAline1.scene().removeItem(self.TAline1) # if (not self.debugTriangle is None and not self.debugTriangle.scene() is None): # self.debugTriangle.scene().removeItem(self.debugTriangle) def drawIt(self, ): ''' for consistency, drawIt should be used instead of drawRelationship ''' self.drawRelationship() def drawRelationship(self, ): # relationship is between two different nodes if self.startNZID != self.endNZID: # set the line type if it hasn't been determined yet. This happens on first draw if self.lineType is None: if self.scene.parent.anyRels(self.relationInstance.startNZID, self.relationInstance.endNZID): self.lineType = CURVE else: self.lineType = STRAIGHT # now draw the line or arc if self.lineType == CURVE: self.drawRel() else: self.drawStraightRel() # relationship is between the same node if self.startNZID == self.endNZID: self.drawBunnyEars() def drawBunnyEars(self, ): # force the rel instance to update its values in case it has been updated from another diagram or the tree view self.relationInstance.reloadDictValues() # get the format in case it changed self.getFormat() # if the arc and text graphics items already exist on the scene then delete them self.clearItem() # draw the relationship arc pen = self.relFormat.pen() brush = self.relFormat.brush() brush.setColor(QColor(Qt.white)) # create an ellipse like the startNode x = self.startNode.INode.sceneBoundingRect().center().x() y = self.startNode.INode.sceneBoundingRect().center().y() w = self.startNode.INode.sceneBoundingRect().width()/2.0 h = self.startNode.INode.sceneBoundingRect().height()/2.0 myEllipse = Ellipse(x, y, w, h) # move the bunny ears around the ellipse until we run out of room startDegree = 360 - (self.numRels * 40) if startDegree < 40: startDegree = 40 startPoint = myEllipse.pointFromAngle(radians(startDegree)) endPoint = myEllipse.pointFromAngle(radians(startDegree-35)) lineMidPoint = centerLine (startPoint[0], startPoint[1], endPoint[0], endPoint[1]) lineDist = distanceLine (startPoint[0], startPoint[1], endPoint[0], endPoint[1]) circleRad = lineDist/2.0 self.bunnyEar = QGraphicsEllipseItem(QRectF(lineMidPoint[0]-circleRad,lineMidPoint[1]-circleRad,circleRad*2,circleRad*2), parent=None) self.bunnyEar.setZValue(RELATIONLAYER) self.bunnyEar.setBrush(brush) self.bunnyEar.setPen(pen) self.bunnyEar.setFlag(QGraphicsItem.ItemIsMovable, True) self.bunnyEar.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.bunnyEar.setFlag(QGraphicsItem.ItemIsSelectable, False) self.bunnyEar.setSelected(False) # create arrowhead self.circlex = lineMidPoint[0] self.circley = lineMidPoint[1] # self.circleDot = QGraphicsEllipseItem(QRectF(self.circlex-1.5,self.circley-1.5,3, 3), parent=None) # self.circleDot.setZValue(NODELAYER) # self.scene.addItem(self.circleDot) # self.pointDot = QGraphicsEllipseItem(QRectF(endPoint[0]-1.5,endPoint[1]-1.5,3, 3), parent=None) # self.pointDot.setZValue(NODELAYER) # self.scene.addItem(self.pointDot) # compute the arrow base center point arrowLen = 5 # this is half the distance of the arrowhead base which determines how wide the arrow head is # compute a point on the arc that is a short distance away from the edge of the ellipse. this is the base of the arrow head # get the slope of the line from the start point to the end point on the node ellipse if endPoint[0] == startPoint[0]: startPoint[0] = startPoint[0] + .001 # slope1 = (startPoint[1] - endPoint[1])/(startPoint[0] - endPoint[0]) slope1 = (endPoint[1] - startPoint[1] )/(endPoint[0] - startPoint[0]) if slope1 == 1.0: slope1 = 1.001 slope1Squared = slope1 * slope1 perpSlope1 = -1.0 / slope1 perpSlope1Squared = perpSlope1 * perpSlope1 # print("numrels:{} startDegree:{} radius:{} slope:{} perpslope:{}".format(self.numRels, startDegree, circleRad, slope1, perpSlope1)) # now find a point on the line tangent to the point on the bunny ear circle. This is the base of the arrow # ax = (arrowLen / sqrt(1 + (perpSlope1Squared))) # ay = ax * perpSlope1 if lineMidPoint[0] < x: baseX = endPoint[0] - (arrowLen / sqrt(1 + (perpSlope1Squared))) baseY = endPoint[1] - ((arrowLen / sqrt(1 + (perpSlope1Squared))) * perpSlope1 ) else: baseX = endPoint[0] + (arrowLen / sqrt(1 + (perpSlope1Squared))) baseY = endPoint[1] + ((arrowLen / sqrt(1 + (perpSlope1Squared))) * perpSlope1 ) # print("ax:{} ay:{} slope1:{} perslope1:{}".format(ax, ay, slope1, perpSlope1)) # self.anchorDot = QGraphicsEllipseItem(QRectF(baseX-1.5,baseY-1.5,3, 3), parent=None) # self.anchorDot.setZValue(NODELAYER) # self.scene.addItem(self.anchorDot) # get first arrowhead base corner ab1dx = (arrowLen / sqrt(1 + (slope1Squared))) ab1dy = ab1dx * slope1 self.b1x = baseX + ab1dx self.b1y = baseY + ab1dy # get 2nd arrowhead base corner ab2dx = (arrowLen / sqrt(1 + (slope1Squared))) ab2dy = ab2dx * slope1 self.b2x = baseX - ab2dx self.b2y = baseY - ab2dy # # calculate the arrowhead points # cx = self.endNode.INode.sceneBoundingRect().center().x() # cy = self.endNode.INode.sceneBoundingRect().center().y() # self.calcArrowHead(QPointF(cx, cy), QPointF( endPoint[0], endPoint[1]), bunnyEars=True) #create an empty polygon arrowPolygon = QPolygonF() # # add arrowhead points # arrowPolygon.append(QPointF(self.ah1x, self.ah1y)) # arrowPolygon.append(QPointF(endPoint[0], endPoint[1])) # arrowPolygon.append(QPointF(self.ah2x, self.ah2y)) # add arrowhead points arrowPolygon.append(QPointF(self.b1x, self.b1y)) arrowPolygon.append(QPointF(endPoint[0], endPoint[1])) arrowPolygon.append(QPointF(self.b2x, self.b2y)) self.arrowHead = QGraphicsPolygonItem(arrowPolygon, parent=None, ) self.arrowHead.setZValue(RELATIONLAYER) self.arrowHead.setBrush(brush) self.arrowHead.setPen(pen) self.arrowHead.setFlag(QGraphicsItem.ItemIsMovable, True) self.arrowHead.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.arrowHead.setFlag(QGraphicsItem.ItemIsSelectable, False) self.arrowHead.setSelected(False) # set data in the bunnyEar object self.bunnyEar.setData(NODEID, self.relationInstance.NZID) self.bunnyEar.setData(ITEMTYPE, RELINSTANCEARC) self.scene.addItem(self.bunnyEar) self.scene.addItem(self.arrowHead) # calculate position of text bunnyEllipse = Ellipse(lineMidPoint[0],lineMidPoint[1],circleRad,circleRad) # use the same midpoint angle as the bunny ear start and end point bunnyMidPoint = bunnyEllipse.pointFromAngle(radians(startDegree-17.5)) # draw the text self.drawText(bunnyMidPoint[0], bunnyMidPoint[1], startPoint[0], startPoint[1], endPoint[0], endPoint[1]) # self.anchorDot = QGraphicsEllipseItem(QRectF(bunnyMidPoint[0]-1.5,bunnyMidPoint[1]-1.5,3, 3), parent=None) # self.anchorDot.setZValue(NODELAYER) # self.scene.addItem(self.anchorDot) def drawStraightRel(self): # print("draw straight rel") # force the rel instance to update its values in case it has been updated from another diagram or the tree view self.relationInstance.reloadDictValues() # get the format in case it changed self.getFormat() # if the arc and text graphics items already exist on the scene then delete them self.clearItem() # draw the relationship arc pen = self.relFormat.pen() brush = self.relFormat.brush() # get the centerpoint of the end node cx = self.endNode.INode.sceneBoundingRect().center().x() cy = self.endNode.INode.sceneBoundingRect().center().y() # get the start and end points of the line esx, esy, eex, eey = self.calcLine3() # create the arc qgraphicsitem self.IRel = QGraphicsLineItem(esx, esy, eex, eey, parent=None) arrowLine = self.IRel # text location tx = self.IRel.line().pointAt(.5).x() ty = self.IRel.line().pointAt(.5).y() # draw the relationship line pen = self.relFormat.pen() # configure the lines and add them to the scene self.IRel.setPen(pen) self.IRel.setZValue(RELATIONLAYER) self.IRel.setData(NODEID, self.relationInstance.NZID) self.IRel.setData(ITEMTYPE, RELINSTANCEARC) self.IRel.setFlag(QGraphicsItem.ItemIsSelectable, True) self.scene.addItem(self.IRel) # line arrowhead # compute the arrow base center point arrowLen = 5 # this is half the distance of the arrowhead base which determines how wide the arrow head is # compute a point on the arc that is a short distance away from the edge of the ellipse. this is the base of the arrow head ax = self.IRel.line().pointAt(1-(10.0/self.IRel.line().length())).x() ay = self.IRel.line().pointAt(1-(10.0/self.IRel.line().length())).y() # compute the arrow base corners if eex == ax: ax = ax + .001 # get the slope of the line from the ellipse to the arrowhead base slope = (eey - ay)/(eex - ax) if slope == 1.0: slope = 1.001 # get the perpindicular slope perpSlope = -1.0 / slope perpSlopeSquared = perpSlope * perpSlope # get first arrowhead base corner ab1dx = (arrowLen / sqrt(1 + (perpSlopeSquared))) ab1dy = ab1dx * perpSlope self.b1x = ax + ab1dx self.b1y = ay + ab1dy # get 2nd arrowhead base corner ab2dx = (arrowLen / sqrt(1 + (perpSlopeSquared))) ab2dy = ab2dx * perpSlope self.b2x = ax - ab2dx self.b2y = ay - ab2dy #create an empty polygon arrowPolygon = QPolygonF() # add arrowhead points arrowPolygon.append(QPointF(self.b1x, self.b1y)) arrowPolygon.append(QPointF(eex, eey)) arrowPolygon.append(QPointF(self.b2x, self.b2y)) self.arrowHead = QGraphicsPolygonItem(arrowPolygon, parent=None, ) self.arrowHead.setZValue(RELATIONLAYER) self.arrowHead.setBrush(brush) self.arrowHead.setPen(pen) self.arrowHead.setFlag(QGraphicsItem.ItemIsMovable, True) self.arrowHead.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.arrowHead.setFlag(QGraphicsItem.ItemIsSelectable, False) self.arrowHead.setSelected(False) self.scene.addItem(self.arrowHead) # draw the text self.drawText(tx, ty, esx, esy, eex, eey) def drawRel(self, ): # draw the curved relationship line # print("draw curve rel") # force the rel instance to update its values in case it has been updated from another diagram or the tree view self.relationInstance.reloadDictValues() # get the format in case it changed self.getFormat() # if the arc and text graphics items already exist on the scene then delete them self.clearItem() # draw the relationship arc pen = self.relFormat.pen() brush = self.relFormat.brush() # METHOD 1 use ellipse center points # # get the center points of the qgraphicellipses # sx = self.startNode.INode.sceneBoundingRect().center().x() # sy = self.startNode.INode.sceneBoundingRect().center().y() # ex = self.endNode.INode.sceneBoundingRect().center().x() # ey = self.endNode.INode.sceneBoundingRect().center().y() # # if the x's or y's are equal offset one slightly to avoid divide by zero errors # if sx == ex: # ex = ex + .001 # if sy == ey: # ey = ey + .001 # self.IRel = RelArc(parent=None, sx=sx, sy=sy, ex=ex, ey=ey, pen=pen, brush=brush, numRels=self.numRels) # METHOD 2 use points on the ellipse # esx, esy, eex, eey = self.calcLine() # self.IRel = RelArc(parent=None, sx=esx, sy=esy, ex=eex, ey=eey, pen=pen, brush=brush, numRels=self.numRels) # METHOD 3 use regularly spaced endpoints on the ellipse # get the centerpoint of the end node cx = self.endNode.INode.sceneBoundingRect().center().x() cy = self.endNode.INode.sceneBoundingRect().center().y() # get the start and end points of the arc esx, esy, eex, eey = self.calcLine2() # create the arc qgraphicsitem self.IRel = RelArc(parent=None, sx=esx, sy=esy, ex=eex, ey=eey, cx=cx, cy=cy, pen=pen, brush=brush, numRels=self.numRels) #create an empty polygon arrowPolygon = QPolygonF() # add arrowhead points arrowPolygon.append(QPointF(self.IRel.b1x, self.IRel.b1y)) arrowPolygon.append(QPointF(eex, eey)) arrowPolygon.append(QPointF(self.IRel.b2x, self.IRel.b2y)) self.arrowHead = QGraphicsPolygonItem(arrowPolygon, parent=None, ) self.arrowHead.setZValue(RELATIONLAYER) self.arrowHead.setBrush(brush) self.arrowHead.setPen(pen) self.arrowHead.setFlag(QGraphicsItem.ItemIsMovable, True) self.arrowHead.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.arrowHead.setFlag(QGraphicsItem.ItemIsSelectable, False) self.arrowHead.setSelected(False) # set data in the RelLine object self.IRel.setData(NODEID, self.relationInstance.NZID) self.IRel.setData(ITEMTYPE, RELINSTANCEARC) # add the RelLine object to the scene self.scene.addItem(self.IRel) # add arrowhead to the scene self.scene.addItem(self.arrowHead) # draw the text self.drawText(self.IRel.mx, self.IRel.my, esx, esy, eex, eey) def drawText(self, mx, my, esx, esy, eex, eey): # mx,my is the center point of the arc # esx,esy is the start point of the arc # eex,eey is the end point of the arc # get the center points of the node qgraphicellipses sx = self.startNode.INode.sceneBoundingRect().center().x() sy = self.startNode.INode.sceneBoundingRect().center().y() sh = self.startNode.INode.sceneBoundingRect().height() ex = self.endNode.INode.sceneBoundingRect().center().x() ey = self.endNode.INode.sceneBoundingRect().center().y() eh = self.endNode.INode.sceneBoundingRect().height() # if the x's or y's are equal offset one slightly to avoid divide by zero errors if sx == ex: ex = ex + .001 if sy == ey: ey = ey + .001 # draw the text # self.IRtext = QGraphicsTextItem(self.relationInstance.relName, parent=None) self.IRtext = RelText(parent=None) # self.IRtext.setZValue(RELATIONLAYER) # self.IRtext.setFlag(QGraphicsItem.ItemIsMovable, True) # self.IRtext.setFlag(QGraphicsItem.ItemIsSelectable, True) # self.IRtext.setSelected(True) # self.IRtext.setAcceptHoverEvents(True) self.IRtext.setData(NODEID, self.relationInstance.NZID) self.IRtext.setData(ITEMTYPE, RELINSTANCETEXT) self.IRtext.setHtml(self.genRelHTML()) #calculate rotation angle adj = abs(esx-eex) opp = abs(esy-eey) OOA = opp/adj rotateAnglerad = (atan(OOA)) rotateAngledeg = degrees(atan(OOA)) # get the height and width of the text graphics item th = self.IRtext.boundingRect().height() tw = self.IRtext.boundingRect().width() # position the relation name text centered on the arc centerpoint if self.startNZID != self.endNZID: # esx, esy, eex, eey if esy >= eey and esx <= eex: # end node is above and to the right self.IRtext.setRotation((360 - rotateAngledeg)) xoffset = (tw/2) * cos(rotateAnglerad) yoffset = (tw/2) * sin(rotateAnglerad) self.IRtext.setPos(QPointF(mx - xoffset, my + yoffset )) elif esy <= eey and esx <= eex: # end node is below and to the right self.IRtext.setRotation(rotateAngledeg) xoffset = (tw/2) * (cos(rotateAnglerad)) yoffset = (tw/2) * (sin(rotateAnglerad)) self.IRtext.setPos(QPointF(mx - xoffset, my - yoffset )) elif esy >= eey and esx >= eex: # end node is above and to the left self.IRtext.setRotation(rotateAngledeg) xoffset = (tw/2) * (cos(rotateAnglerad)) yoffset = (tw/2) * (sin(rotateAnglerad)) self.IRtext.setPos(QPointF(mx - xoffset, my - yoffset )) elif esy <= eey and esx >= eex: # end node is below and to the left self.IRtext.setRotation((360 - rotateAngledeg)) xoffset = (tw/2) * cos(rotateAnglerad) yoffset = (tw/2) * sin(rotateAnglerad) self.IRtext.setPos(QPointF(mx - xoffset, my + yoffset )) # this shouldn't happen else: xoffset = (tw/2) yoffset = (tw/2) self.IRtext.setPos(QPointF(mx, my )) # print("tw {} xoffset {} yoffset {} angle {}".format(tw, xoffset, yoffset, rotateAngledeg)) elif self.startNZID == self.endNZID: if mx > sx: self.IRtext.setPos(QPointF(mx, my - (th) )) if mx < sx: self.IRtext.setPos(QPointF(mx - tw, my - (th) )) else: # this shouldn't happen self.IRtext.setPos(QPointF(mx, my )) self.scene.addItem(self.IRtext) def updateText(self, ): # force the node instance to update its values in case it has been updated from another diagram or the tree view self.relationInstance.reloadDictValues() # self.IRtext.setPlainText(self.relationInstance.relName) self.IRtext.setHtml(self.genRelHTML()) def genRelHTML(self): '''generate html to display the relationship type ''' prefix = '<html><body>' suffix = "</body></html>" myHTML = ('{}<p><font size="1"> [{}]</font></p>{}'.format(prefix, self.relationInstance.relName, suffix)) return myHTML # def calcLine(self, ): # '''calculate the points on the two ellipses to draw a straight line through their centers''' # self.startNodeX = self.startNode.INode.sceneBoundingRect().center().x() # self.startNodeY = self.startNode.INode.sceneBoundingRect().center().y() # self.endNodeX = self.endNode.INode.sceneBoundingRect().center().x() # self.endNodeY = self.endNode.INode.sceneBoundingRect().center().y() # # self.distanceX = abs(self.startNodeX - self.endNodeX) + .001 # self.distanceY = abs(self.startNodeY - self.endNodeY) + .001 # temp = (pow(self.distanceX,2) + pow(self.distanceY,2)) - (2 * self.distanceX * self.distanceY * cos(radians(90))) # self.distanceXY = sqrt(temp) # a = self.distanceX # b = self.distanceY # c = self.distanceXY ## angA = angle(a,b,c) ## angB = angle(b,c,a) # angC = angleDegrees(c,a,b) # # if self.endNodeX == self.startNodeX: # self.endNodeX = self.endNodeX + .001 # if self.endNodeY == self.startNodeY: # self.endNodeY = self.endNodeY + .001 # # if self.endNodeX > self.startNodeX and self.endNodeY > self.startNodeY: # startangle = angC # endangle = 180 + angC # elif self.endNodeX < self.startNodeX and self.endNodeY > self.startNodeY: # startangle = 180 - angC # endangle = 360 - angC # elif self.endNodeX < self.startNodeX and self.endNodeY < self.startNodeY: # startangle = 180 + angC # endangle = angC # elif self.endNodeX > self.startNodeX and self.endNodeY < self.startNodeY: # startangle = 360 - angC # endangle = 180 - angC # # #print("angA: {} angleB: {} angleC: {} startangle: {}".format(angA, angB, angC, startangle)) # aa = self.startNode.INode.boundingRect().width()/2.0 # bb = self.startNode.INode.boundingRect().height()/2.0 # sn = Ellipse(self.startNodeX, self.startNodeY, aa, bb) # sx, sy = sn.pointFromAngle(radians(startangle)) # cc = self.endNode.INode.boundingRect().width()/2.0 # dd = self.endNode.INode.boundingRect().height()/2.0 # en = Ellipse(self.endNodeX, self.endNodeY, cc, dd) #need to calc width/height of end node. # ex, ey = en.pointFromAngle(radians(endangle)) # #print("sx {} - sy {} - ex {} - ey {}".format(sx,sy,ex,sy)) # return sx, sy, ex, ey def calcLine2(self, ): '''calculate the point on the start and end node ellipse for an arc ''' # get centerpoints of the two nodes self.startNodeX = self.startNode.INode.sceneBoundingRect().center().x() self.startNodeY = self.startNode.INode.sceneBoundingRect().center().y() self.endNodeX = self.endNode.INode.sceneBoundingRect().center().x() self.endNodeY = self.endNode.INode.sceneBoundingRect().center().y() self.distanceX = abs(self.startNodeX - self.endNodeX) + .001 self.distanceY = abs(self.startNodeY - self.endNodeY) + .001 temp = (pow(self.distanceX,2) + pow(self.distanceY,2)) - (2 * self.distanceX * self.distanceY * cos(radians(90))) self.distanceXY = sqrt(temp) a = self.distanceX b = self.distanceY c = self.distanceXY # angA = angle(a,b,c) # angB = angle(b,c,a) angC = angleDegrees(c,a,b) # print("angC {}".format(angC)) if self.endNodeX == self.startNodeX: self.endNodeX = self.endNodeX + .001 if self.endNodeY == self.startNodeY: self.endNodeY = self.endNodeY + .001 # adjust starting and ending points based on the number of rels between the node, if more than 7 in the same direction just start stacking them one on top of the other. if self.numRels > 6: adjustDegree = (7) * 10 else: adjustDegree = (self.numRels+1) * 10 if self.endNodeX > self.startNodeX and self.endNodeY > self.startNodeY: startangle = angC - adjustDegree endangle = 180 + angC + adjustDegree elif self.endNodeX < self.startNodeX and self.endNodeY > self.startNodeY: startangle = 180 - angC - adjustDegree endangle = 360 - angC + adjustDegree elif self.endNodeX < self.startNodeX and self.endNodeY < self.startNodeY: startangle = 180 + angC - adjustDegree endangle = angC + adjustDegree elif self.endNodeX > self.startNodeX and self.endNodeY < self.startNodeY: startangle = 360 - angC - adjustDegree endangle = 180 - angC + adjustDegree # print("adjust: {}startangle: {} endangle: {}".format(adjustDegree, startangle, endangle)) aa = self.startNode.INode.boundingRect().width()/2.0 bb = self.startNode.INode.boundingRect().height()/2.0 sn = Ellipse(self.startNodeX, self.startNodeY, aa, bb) sx, sy = sn.pointFromAngle(radians(startangle)) cc = self.endNode.INode.boundingRect().width()/2.0 dd = self.endNode.INode.boundingRect().height()/2.0 en = Ellipse(self.endNodeX, self.endNodeY, cc, dd) #need to calc width/height of end node. ex, ey = en.pointFromAngle(radians(endangle)) #print("sx {} - sy {} - ex {} - ey {}".format(sx,sy,ex,sy)) return sx, sy, ex, ey def calcLine3(self, ): '''calculate the point on the start and end node ellipse for a straight line ''' # get centerpoints of the two nodes self.startNodeX = self.startNode.INode.sceneBoundingRect().center().x() self.startNodeY = self.startNode.INode.sceneBoundingRect().center().y() self.endNodeX = self.endNode.INode.sceneBoundingRect().center().x() self.endNodeY = self.endNode.INode.sceneBoundingRect().center().y() self.distanceX = abs(self.startNodeX - self.endNodeX) + .001 self.distanceY = abs(self.startNodeY - self.endNodeY) + .001 temp = (pow(self.distanceX,2) + pow(self.distanceY,2)) - (2 * self.distanceX * self.distanceY * cos(radians(90))) self.distanceXY = sqrt(temp) a = self.distanceX b = self.distanceY c = self.distanceXY # angA = angle(a,b,c) # angB = angle(b,c,a) angC = angleDegrees(c,a,b) # print("angC {}".format(angC)) if self.endNodeX == self.startNodeX: self.endNodeX = self.endNodeX + .001 if self.endNodeY == self.startNodeY: self.endNodeY = self.endNodeY + .001 # # adjust starting and ending points based on the number of rels between the node, if more than 7 in the same direction just start stacking them one on top of the other. # if self.numRels > 6: # adjustDegree = (7) * 10 # else: # adjustDegree = (self.numRels+1) * 10 adjustDegree = 0 if self.endNodeX > self.startNodeX and self.endNodeY > self.startNodeY: startangle = angC - adjustDegree endangle = 180 + angC + adjustDegree elif self.endNodeX < self.startNodeX and self.endNodeY > self.startNodeY: startangle = 180 - angC - adjustDegree endangle = 360 - angC + adjustDegree elif self.endNodeX < self.startNodeX and self.endNodeY < self.startNodeY: startangle = 180 + angC - adjustDegree endangle = angC + adjustDegree elif self.endNodeX > self.startNodeX and self.endNodeY < self.startNodeY: startangle = 360 - angC - adjustDegree endangle = 180 - angC + adjustDegree # print("adjust: {}startangle: {} endangle: {}".format(adjustDegree, startangle, endangle)) aa = self.startNode.INode.boundingRect().width()/2.0 bb = self.startNode.INode.boundingRect().height()/2.0 sn = Ellipse(self.startNodeX, self.startNodeY, aa, bb) sx, sy = sn.pointFromAngle(radians(startangle)) cc = self.endNode.INode.boundingRect().width()/2.0 dd = self.endNode.INode.boundingRect().height()/2.0 en = Ellipse(self.endNodeX, self.endNodeY, cc, dd) #need to calc width/height of end node. ex, ey = en.pointFromAngle(radians(endangle)) #print("sx {} - sy {} - ex {} - ey {}".format(sx,sy,ex,sy)) return sx, sy, ex, ey # def moveRelationshipLine(self, ): # self.drawRelationship() def getObjectDict(self, ): objectDict = {} objectDict["NZID"] = self.relationInstance.NZID objectDict["diagramType"] = self.diagramType return objectDict
class EdgeItem(GraphItem): _qt_pen_styles = { 'dashed': Qt.DashLine, 'dotted': Qt.DotLine, 'solid': Qt.SolidLine, } def __init__(self, highlight_level, spline, label_center, label, from_node, to_node, parent=None, penwidth=1, edge_color=None, style='solid'): super(EdgeItem, self).__init__(highlight_level, parent) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._default_edge_color = self._COLOR_BLACK if edge_color is not None: self._default_edge_color = edge_color self._default_text_color = self._COLOR_BLACK self._default_color = self._COLOR_BLACK self._text_brush = QBrush(self._default_color) self._shape_brush = QBrush(self._default_color) if style in ['dashed', 'dotted']: self._shape_brush = QBrush(Qt.transparent) self._label_pen = QPen() self._label_pen.setColor(self._default_text_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._edge_pen = QPen(self._label_pen) self._edge_pen.setWidth(penwidth) self._edge_pen.setColor(self._default_edge_color) self._edge_pen.setStyle(self._qt_pen_styles.get(style, Qt.SolidLine)) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) self._label.setFont(GraphItem._LABEL_FONT) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) self._label.hoverEnterEvent = self._handle_hoverEnterEvent self._label.hoverLeaveEvent = self._handle_hoverLeaveEvent self._label.setAcceptHoverEvents(True) # spline specification according to # http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if (coordinates[0].startswith('e,')): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if (coordinates[0].startswith('s,')): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._arrow.hoverEnterEvent = self._handle_hoverEnterEvent self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent self._arrow.setAcceptHoverEvents(True) self._path = QGraphicsPathItem(parent) self._path.setPath(path) self.addToGroup(self._path) self.set_node_color() self.set_label_color() def add_to_scene(self, scene): scene.addItem(self) if self._label is not None: scene.addItem(self._label) if self._arrow is not None: scene.addItem(self._arrow) def setToolTip(self, tool_tip): super(EdgeItem, self).setToolTip(tool_tip) if self._label is not None: self._label.setToolTip(tool_tip) if self._arrow is not None: self._arrow.setToolTip(tool_tip) def add_sibling_edge(self, edge): self._sibling_edges.add(edge) def set_node_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) self._text_brush.setColor(self._default_color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(self._default_edge_color) self._edge_pen.setColor(self._default_edge_color) else: self._label_pen.setColor(color) self._text_brush.setColor(color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(color) self._edge_pen.setColor(color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._shape_brush) self._arrow.setPen(self._edge_pen) def set_label_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) else: self._label_pen.setColor(color) if self._label is not None: self._label.setBrush(self._text_brush) self._label.setPen(self._label_pen) def _handle_hoverEnterEvent(self, event): # hovered edge item in red self.set_node_color(self._COLOR_RED) self.set_label_color(self._COLOR_RED) if self._highlight_level > 1: if self.from_node != self.to_node: # from-node in blue self.from_node.set_node_color(self._COLOR_BLUE) # to-node in green self.to_node.set_node_color(self._COLOR_GREEN) else: # from-node/in-node in teal self.from_node.set_node_color(self._COLOR_TEAL) self.to_node.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: # sibling edges in orange for sibling_edge in self._sibling_edges: sibling_edge.set_node_color(self._COLOR_ORANGE) def _handle_hoverLeaveEvent(self, event): self.set_node_color() self.set_label_color() if self._highlight_level > 1: self.from_node.set_node_color() self.to_node.set_node_color() if self._highlight_level > 2: for sibling_edge in self._sibling_edges: sibling_edge.set_node_color()
class MorphingApp(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(MorphingApp, self).__init__(parent) self.setupUi(self) self.btnBlend.setEnabled(False) self.chkTriangles.setEnabled(False) self.sliderAlpha.setEnabled(False) self.startSetImg = QGraphicsScene() self.endSetImg = QGraphicsScene() self.Morpher = QGraphicsScene() self.initialPoints = False self.addPoints = False self.comfirm = False # QGraphicsScene() # self.leftImg = #self.rightImg = self.state = "first_state" self.gfxLeft.setScene(self.startSetImg) self.gfxRight.setScene(self.endSetImg) self.gfxBlendImg.setScene(self.Morpher) self.btnStartImg.clicked.connect(self.loadLeftImage) self.btnEndImg.clicked.connect(self.loadRightImage) self.sliderAlpha.valueChanged.connect(self.setAlpha) self.chkTriangles.stateChanged.connect(self.triangulation) self.btnBlend.clicked.connect(self.getImageAtAlpha) self.gfxLeft.mousePressEvent = self.setLeftPoints self.gfxRight.mousePressEvent = self.setRightPoints self.centralwidget.mousePressEvent = self.secondWaySave self.keyPressEvent = self.BackSpace #self.retriangulation() def loadLeftImage(self): """ *** DO NOT MODIFY THIS METHOD! *** Obtain a file name from a file dialog, and pass it on to the loading method. This is to facilitate automated testing. Invoke this method when clicking on the 'load' button. You must modify the method below. """ self.filePath, _ = QFileDialog.getOpenFileName( self, caption='Open JPG file ...', filter="Image (*.jpg *.png)") if not self.filePath: return self.startImage = imageio.imread(self.filePath) self.startSetImg.clear() self.startSetImg.addPixmap(QPixmap(self.filePath)) self.gfxLeft.fitInView(self.startSetImg.itemsBoundingRect(), QtCore.Qt.KeepAspectRatio) self.LeftimgPoints = self.filePath + '.txt' try: fh = open(self.LeftimgPoints, 'r') redPen = QPen(QtCore.Qt.red) redBrush = QBrush(QtCore.Qt.red) self.leftPoints = np.loadtxt(self.LeftimgPoints) self.initialPoints = True for x, y in self.leftPoints: self.startSetImg.addEllipse(x, y, 20, 20, redPen, redBrush) except FileNotFoundError: open(self.LeftimgPoints, 'w').close() self.initialPoints = False self.addPoints = False def loadRightImage(self): """ *** DO NOT MODIFY THIS METHOD! *** Obtain a file name from a file dialog, and pass it on to the loading method. This is to facilitate automated testing. Invoke this method when clicking on the 'load' button. You must modify the method below. """ self.filePath1, _ = QFileDialog.getOpenFileName( self, caption='Open JPG file ...', filter="Image (*.jpg *.png)") if not self.filePath1: return self.endImage = imageio.imread(self.filePath1) self.endSetImg.clear() self.endSetImg.addPixmap(QPixmap(self.filePath1)) self.gfxRight.fitInView(self.endSetImg.itemsBoundingRect(), QtCore.Qt.KeepAspectRatio) self.btnBlend.setEnabled(True) self.chkTriangles.setEnabled(True) self.sliderAlpha.setEnabled(True) self.txtAlpha.setEnabled(True) #load point correspondence self.RightimgPoints = self.filePath1 + '.txt' try: fh = open(self.RightimgPoints, 'r') self.rightPoints = np.loadtxt(self.RightimgPoints) redPen = QPen(QtCore.Qt.red) print(self.rightPoints) redBrush = QBrush(QtCore.Qt.red) self.initialPoints = True for x, y in self.rightPoints: self.endSetImg.addEllipse(x, y, 20, 20, redPen, redBrush) except FileNotFoundError: open(self.RightimgPoints, 'w').close() self.initialPoints = False self.addPoints = False def setAlpha(self): self.txtAlpha.setText(str(self.sliderAlpha.value() / 20.0)) def retriangulation(self): if self.state == "right_set": if self.chkTriangles.isChecked(): self.chkTriangles.setChecked(False) self.chkTriangles.setChecked(True) def triangulation(self): if self.chkTriangles.isChecked() == True: self.tri1 = [] self.tri2 = [] self.leftSimplices = Delaunay(self.leftPoints).simplices if self.initialPoints == True and self.addPoints == True: Pen = QPen(QtCore.Qt.cyan) print(1) elif self.addPoints == True: Pen = QPen(QtCore.Qt.blue) print(2) else: Pen = QPen(QtCore.Qt.red) for triangle in self.leftSimplices.tolist(): #TriLeft = self.leftPoints[triangle] #TriRight = self.rightPoints[triangle] #leftTriangles.append(Tri) # print(self.leftPoints[triangle]) #print(self.leftPoints[triangle[0]]) pointA = QtCore.QPointF(self.leftPoints[triangle[0]][0], self.leftPoints[triangle[0]][1]) PointB = QtCore.QPointF(self.leftPoints[triangle[1]][0], self.leftPoints[triangle[1]][1]) PointC = QtCore.QPointF(self.leftPoints[triangle[2]][0], self.leftPoints[triangle[2]][1]) self.drawTriangle = QtGui.QPolygonF([pointA, PointB, PointC]) self.tri1Item = QGraphicsPolygonItem(self.drawTriangle) self.tri1Item.setPen(Pen) self.startSetImg.addItem(self.tri1Item) self.tri1.append(self.tri1Item) pointAA = QtCore.QPointF(self.rightPoints[triangle[0]][0], self.rightPoints[triangle[0]][1]) PointBB = QtCore.QPointF(self.rightPoints[triangle[1]][0], self.rightPoints[triangle[1]][1]) PointCC = QtCore.QPointF(self.rightPoints[triangle[2]][0], self.rightPoints[triangle[2]][1]) self.drawTriangle1 = QtGui.QPolygonF( [pointAA, PointBB, PointCC]) self.tri2Item = QGraphicsPolygonItem(self.drawTriangle1) self.tri2Item.setPen(Pen) self.endSetImg.addItem(self.tri2Item) self.tri2.append(self.tri2Item) else: #print("111111111") for item1 in self.tri1: self.startSetImg.removeItem(item1) for item2 in self.tri2: self.endSetImg.removeItem(item2) def getImageAtAlpha(self): triangleTuple = loadTriangles(self.filePath + '.txt', self.filePath1 + '.txt') #img = Morpher(leftImage, triangleTuple[0], rightImage, triangleTuple[1]).getImageAtAlpha() alpha = self.sliderAlpha.value() img = Morpher(self.startImage, triangleTuple[0], self.endImage, triangleTuple[1]).getImageAtAlpha(alpha / 20.0) imgQt = QImage(ImageQt.ImageQt(Image.fromarray(img))) result = QPixmap.fromImage(imgQt) self.Morpher.addPixmap(result) self.gfxBlendImg.fitInView(self.Morpher.itemsBoundingRect(), QtCore.Qt.KeepAspectRatio) def secondWaySave(self, e): if self.state == "right_set": self.comfirmPoints() self.state = "first_state" def setLeftPoints(self, e): if self.state == "first_state": ## if it contains points already self.leftPoint = self.gfxLeft.mapToScene(e.pos()) greenPen = QPen(QtCore.Qt.green) greenBrush = QBrush(QtCore.Qt.green) self.leftPointItem = QGraphicsEllipseItem(self.leftPoint.x(), self.leftPoint.y(), 20, 20) self.leftPointItem.setPen(greenPen) self.leftPointItem.setBrush(greenBrush) self.startSetImg.addItem(self.leftPointItem) self.state = "left_set" elif self.state == "right_set": self.comfirmPoints() ## if it contains points already self.leftPoint = self.gfxLeft.mapToScene(e.pos()) greenPen = QPen(QtCore.Qt.green) greenBrush = QBrush(QtCore.Qt.green) self.leftPointItem = QGraphicsEllipseItem(self.leftPoint.x(), self.leftPoint.y(), 20, 20) self.leftPointItem.setPen(greenPen) self.leftPointItem.setBrush(greenBrush) self.startSetImg.addItem(self.leftPointItem) self.state = "left_set" def setRightPoints(self, e): if self.state == "left_set": ## if it contains points already self.rightPoint = self.gfxRight.mapToScene(e.pos()) greenPen = QPen(QtCore.Qt.green) greenBrush = QBrush(QtCore.Qt.green) self.rightPointItem = QGraphicsEllipseItem(self.rightPoint.x(), self.rightPoint.y(), 20, 20) self.rightPointItem.setPen(greenPen) self.rightPointItem.setBrush(greenBrush) self.endSetImg.addItem(self.rightPointItem) self.state = "right_set" def comfirmPoints(self): #print(123) self.addPoints = True self.comfirm = True bluePen = QPen(QtCore.Qt.blue) blueBrush = QBrush(QtCore.Qt.blue) self.startSetImg.removeItem(self.leftPointItem) self.endSetImg.removeItem(self.rightPointItem) self.leftPointItem.setPen(bluePen) self.leftPointItem.setBrush(blueBrush) self.rightPointItem.setPen(bluePen) self.rightPointItem.setBrush(blueBrush) self.endSetImg.addItem(self.rightPointItem) self.startSetImg.addItem(self.leftPointItem) if os.stat(self.LeftimgPoints).st_size == 0 and os.stat( self.RightimgPoints).st_size == 0: self.leftPoints = np.array( [[self.leftPoint.x(), self.leftPoint.y()]]) self.rightPoints = np.array( [[self.rightPoint.x(), self.rightPoint.y()]]) else: self.leftPoints = np.vstack( (self.leftPoints, [self.leftPoint.x(), self.leftPoint.y()])) #self.leftPoints.append([self.leftPoint.x(), self.leftPoint.y()]) #self.rightPoints.append([self.rightPoint.x(), self.rightPoint.y()]) self.rightPoints = np.vstack( (self.rightPoints, [self.rightPoint.x(), self.rightPoint.y()])) self.retriangulation() with open(self.filePath + '.txt', "w") as fin: for point in self.leftPoints.tolist(): fin.write( str((round(point[0], 1))) + ' ' + str((round(point[1], 1))) + '\n') with open(self.filePath1 + '.txt', "w") as fin1: for point in self.rightPoints.tolist(): fin1.write( str((round(point[0], 1))) + ' ' + str((round(point[1], 1))) + '\n') def BackSpace(self, e): key = e.key() if key == QtCore.Qt.Key_Backspace: if self.state == "left_set": self.startSetImg.removeItem(self.leftPointItem) self.state = "first_state" elif self.state == "right_set": self.endSetImg.removeItem(self.rightPointItem) self.state = "left_set"
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 CallOut(): ''' This represents a callouton the diagram This class creates a text item and then surrounds it with a polygon that is a rectangle with a pointer to another object on the diagram. This can be used by hover over functions or be displayed as a part of a node or relationship ''' def __init__(self, scene, text, anchorPoint, diagramType, format): self.scene = scene self.model = self.scene.parent.model self.text = text self.anchorPoint = anchorPoint self.diagramType = diagramType self.format = format # initialize the two qgraphicsitems needed to draw a relationship to None self.itemText = None self.itemPolygon = None self.drawIt def name(self, ): return "no name" def NZID(self, ): return None def clearItem(self, ): if (not self.itemText is None and not self.itemText.scene() is None): self.itemText.scene().removeItem(self.itemText) if (not self.itemPolygon is None and not self.itemPolygon.scene() is None): self.itemPolygon.scene().removeItem(self.itemPolygon) def drawIt(self, ): ''' draw the callout ''' # if the polygon and text graphics items already exist on the scene then delete them self.clearItem() # draw the relationship arc pen = self.format.pen() brush = self.format.brush() # create text box # draw the text self.itemText = QGraphicsTextItem(self.relationInstance.relName, parent=None) self.itemText.setZValue(CALLOUTLAYER) self.itemText.setFlag(QGraphicsItem.ItemIsMovable, True) self.itemText.setFlag(QGraphicsItem.ItemIsSelectable, True) self.itemText.setSelected(False) self.itemText.setData(NODEID, self.relationInstance.NZID) self.itemText.setData(ITEMTYPE, CALLOUT) self.itemText.setHtml(self.genTextHTML()) # set the position of the text self.itemText.setPos(self.anchorPoint) # get the height and width of the text graphics item th = self.IRtext.boundingRect().height() tw = self.IRtext.boundingRect().width() #create an empty polygon arrowPolygon = QPolygonF() # add callout points arrowPolygon.append(self.anchorPoint) arrowPolygon.append( QPointF(self.anchorPoint.x() + tw, self.anchorPoint.y())) arrowPolygon.append( QPointF(self.anchorPoint.x() + tw, self.anchorPoint.y())) arrowPolygon.append( QPointF(self.anchorPoint.x() + tw, self.anchorPoint.y() + th)) arrowPolygon.append( QPointF(self.anchorPoint.x(), self.anchorPoint.y() + th)) self.itemPolygon = QGraphicsPolygonItem( arrowPolygon, parent=None, ) self.itemPolygon.setZValue(CALLOUTLAYER) self.itemPolygon.setBrush(brush) self.itemPolygon.setPen(pen) self.itemPolygon.setFlag(QGraphicsItem.ItemIsMovable, True) self.itemPolygon.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.itemPolygon.setFlag(QGraphicsItem.ItemIsSelectable, False) self.itemPolygon.setSelected(False) # set data in the RelLine object self.IRel.setData(NODEID, self.relationInstance.NZID) self.IRel.setData(ITEMTYPE, RELINSTANCEARC) # add the polygon object to the scene self.scene.addItem(self.itemPolygon) # add text to the scene self.scene.addItem(self.itemText) def updateText(self, ): # # force the node instance to update its values in case it has been updated from another diagram or the tree view # self.relationInstance.reloadDictValues() # self.IRtext.setPlainText(self.relationInstance.relName) self.itemText.setHtml(self.genTextHTML()) def genTextHTML(self): '''generate html to display the text ''' prefix = '<html><body>' suffix = "</body></html>" myHTML = ('{}<p><font size="1"> [{}]</font></p>{}'.format( prefix, self.text, suffix)) return myHTML def moveIt(self, ): self.drawIt()
class Tower(QGraphicsPixmapItem): SCALE_FACTOR = 300 def __init__(self, parent=None): super(Tower, self).__init__(parent) self.setPixmap(QPixmap(':/Resources/images/Tower.png')) self.setScale(0.5) self.points = [ QPointF(1, 0), QPointF(2, 0), QPointF(3, 1), QPointF(3, 2), QPointF(2, 3), QPointF(1, 3), QPointF(0, 2), QPointF(0, 1) ] for point in self.points: point.setX(point.x() * Tower.SCALE_FACTOR) point.setY(point.y() * Tower.SCALE_FACTOR) self.polygon = QPolygonF(self.points) self.border = QGraphicsPolygonItem(self.polygon, self) # self.border.setPen(QPen(Qt.red)) self.border.setScale(0.5) self.border.setPen(QPen(Qt.DashLine)) self.poly_center = QPointF(1.5, 1.5) self.poly_center *= Tower.SCALE_FACTOR self.poly_center = self.mapToScene(self.poly_center) self.tower_center = QPointF(self.x() + 50, self.y() + 50) self.line = QLineF(self.poly_center, self.tower_center) self.border.setPos(self.border.x() + self.line.dx(), self.border.y() + self.line.dy()) # self.attack_destination = QPointF(800,0) self.timer = QTimer() self.timer.timeout.connect(lambda: self.accquire_target()) self.timer.start(1000) def fire(self): arrow = Arrow() arrow.setPos(self.x(), self.y()) attack_line = QLineF(QPointF(self.x() + 25, self.y() + 25), self.attack_destination) line = QGraphicsLineItem(attack_line) line.setPen(QPen(Qt.blue)) # self.scene().addItem(line) attack_angle = -1 * attack_line.angle( ) # Multiplied by -1 because the angle is given in counter clockwise direction arrow.setRotation(attack_angle) self.scene().addItem(arrow) def distance_to(self, item): distance = QLineF(QPointF(self.pos().x() + 25, self.pos().y() + 25), item.pos()) line = QGraphicsLineItem(distance) line.setPen(QPen(Qt.red)) # self.scene().addItem(line) return distance.length() def accquire_target(self): attack_item = self.border.collidingItems() if (len(attack_item) == 1): self.has_target = False return closet_distance = 300 closet_pt = QPointF(0, 0) for item in attack_item: if isinstance(item, Enemy): distance = self.distance_to(item) if (distance < closet_distance): closet_distance = distance closet_pt = QPointF(item.pos().x() + 50, item.pos().y() + 50) self.has_target = True self.attack_destination = closet_pt self.fire()
class DynamicGameObject(StaticGameObject): '''Class to modelate objects that can move and attack (minions, Ia's champion) ''' def __init__(self): super().__init__() # set speed self.speed = 0 self.attack = 1 # set pos / destination self.x_prev = 0 self.y_prev = 0 self.setPos(0, 0) self.destination = QPointF(0, 0) # initialize attack range (area) self.attack_area = QGraphicsPolygonItem() self.attack_dest = QPointF(0, 0) self.has_target = False def set_range(self, SCALE_FACTOR): '''It gives the object a QGraphicsPolygonItem to attack at a certain range ''' # create points vector self.range = SCALE_FACTOR points = [ QPointF(1, 0), QPointF(2, 0), QPointF(3, 1), QPointF(3, 2), QPointF(2, 3), QPointF(1, 3), QPointF(0, 2), QPointF(0, 1) ] # scale points points = [p * SCALE_FACTOR for p in points] # create polygon self.polygon = QPolygonF(points) # create QGraphicsPolygonItem self.attack_area = QGraphicsPolygonItem(self.polygon, self) self.attack_area.setPen(QPen(Qt.DotLine)) # move the polygon poly_center = QPointF(1.5 * SCALE_FACTOR, 1.5 * SCALE_FACTOR) poly_center = self.mapToScene(poly_center) minion_center = QPointF(self.x() + self.pixmap().width() / 2, self.y() + self.pixmap().height() / 2) ln = QLineF(poly_center, minion_center) self.attack_area.setPos(self.x() + ln.dx(), self.y() + ln.dy()) def fire(self): if not self.scene(): return bullet = Bullet(self) bullet.setPos(self.x() + self.pixmap().width() / 2, self.y() + self.pixmap().height() / 2) # set the angle to be paralel to the line that connects the # tower and target ln = QLineF( QPointF(self.x() + self.pixmap().width() / 2, self.y() + self.pixmap().height() / 2), self.attack_dest) angle = -1 * ln.angle() # -1 to make it clock wise bullet.setRotation(angle) self.scene().addItem(bullet) def acquire_target(self): # get a list of all items colliding with attack area colliding_items = self.attack_area.collidingItems() self.has_target = False closest_dist = 300 closest_point = QPointF(0, 0) for i in colliding_items: if hasattr(i, 'team') and i.team != self.team: this_distance = self.distance_to(i) if this_distance < closest_dist: closest_dist = this_distance closest_point = i.pos() self.has_target = True self.attack_dest = closest_point if self.has_target: self.fire() def set_attack(self, value): self.attack = value def set_destination(self, point): '''QPoinF : point ''' self.destination = point def set_speed(self, s): self.speed = s @property def should_be_moving(self): ln = QLineF(self.pos(), self.destination) CLOSE_DIST = 30 if ln.length() > CLOSE_DIST: return True else: return False def move_forward(self): # move object if self.should_be_moving: ln = QLineF(self.pos(), self.destination) ln.setLength(self.speed) self.rotate_to_point(self.destination) # avoid collision colliding_items = self.collidingItems() for i in colliding_items: if isinstance(i, StaticGameObject): collision_line = QLineF(self.pos(), i.pos()) collision_line.setLength(30) self.setPos(self.x() - collision_line.dx(), self.y() - collision_line.dy()) # move object forward at current angle self.setPos(self.x() + ln.dx(), self.y() + ln.dy()) self.x_prev = self.pos().x() self.y_prev = self.pos().y() def distance_to(self, item): '''item: QGraphicsItem ''' ln = QLineF(self.pos(), item.pos()) return ln.length() def set_dest_to_closest(self): '''Sets destination to closest enemy ''' if not self.scene(): return scene_items = self.scene().items() closest_point = QPointF(0, 0) closest_dist = 1000 for i in scene_items: if hasattr(i, 'team') and i.team != self.team: this_distance = self.distance_to(i) if this_distance < closest_dist: closest_dist = this_distance closest_point = i.pos() self.set_destination(closest_point) def rotate_to_point(self, point): '''point: QPointF''' ln = QLineF(self.pos(), point) # that 90 is because sprite sheet is pointing north self.setRotation(-1 * ln.angle() + 90)
def create_cross(): item = QGraphicsPolygonItem(qt_drawings.cross_polygon) item.setBrush(qt_drawings.red_brush) item.setPen(qt_drawings.red_pen) return item
class StoreIcon(QGraphicsPixmapItem): def __init__(self, parent=None): super().__init__() # bool to check if it is available self.available = False # set store gui self.gui = Store() # set graphics self.setPixmap(QPixmap('./res/imgs/blue_chest.png').scaled(80, 80, Qt.KeepAspectRatio)) # create points vector points = [QPointF(1, 0), QPointF(2, 0), QPointF(3, 1), QPointF(3, 2), QPointF(2, 3), QPointF(1, 3), QPointF(0, 2), QPointF(0, 1)] # scale points SCALE_FACTOR = 100 points = [p * SCALE_FACTOR for p in points] # create polygon self.polygon = QPolygonF(points) # create QGraphicsPolygonItem self.available_area = QGraphicsPolygonItem(self.polygon, self) self.available_area.setPen(QPen(Qt.DotLine)) # move the polygon poly_center = QPointF(1.5 * SCALE_FACTOR, 1.5 * SCALE_FACTOR) poly_center = self.mapToScene(poly_center) store_center = QPointF(self.x() + 40, self.y() + 40) ln = QLineF(poly_center, store_center) self.available_area.setPos(self.x() + ln.dx(), self.y() + ln.dy()) # connect the timer to acquire_champion self.timer = QTimer() self.timer.timeout.connect(self.acquire_champion) self.timer.start(1000) def mousePressEvent(self, event): if self.available: self.launch_store() def acquire_champion(self): colliding_items = self.available_area.collidingItems() found = False for item in colliding_items: if isinstance(item, PlayerChampion): self.set_available(True) found = True if not found: self.set_available(False) def set_available(self, bool_input): self.available = bool_input if not self.available and self.gui.isVisible(): self.gui.close() def launch_store(self): self.gui.show()
class GraphicsScene(QGraphicsScene): def __init__(self, annotation_manager, count_label, parent=None): super(GraphicsScene, self).__init__(parent) self.annotations = set() self.negative_annotations = set() self.roi_point_items = set() self.removal_tool = RemovalTool(annotation_manager, self) self.annotation_tool = AnnotationTool(annotation_manager, self) self.currently_drawing = False self.currently_deleting = False self.image_name = None self.image_path = None self.curr_image = None self.annotation_manager = annotation_manager self.count_label = count_label self.saved = True self.enabled = False self.polygon = None self.roi_polygon = None self.picked_tool = 0 self.removal_circle_item = None self.removal_circle_radius = 100 # stack of tuples such as: (add/remove, annotation_item) self.annotation_undo_stack = [] # stack of ints depicting which action needs undoing self.action_undo_stack = [] def add_image(self, image_path): image = QImage(image_path) self.image = image self.original_image = image.copy() h, w = image.height(), image.width() self.pixmap_item = self.addPixmap(QPixmap.fromImage(image)) def change_annotations_on_image(self): print("Changing image annotations") self.enabled = True self.clear() self.annotations = set() self.negative_annotations = set() self.polygon = None self.roi_polygon = None self.removal_circle_item = None self.annotation_undo_stack = [] self.action_undo_stack = [] self.add_image(self.image_path) self.setSceneRect(self.itemsBoundingRect()) for a in self.annotation_manager.annotations_rect[self.image_name]: tmp_annot = AnnotationItem(QPointF(a[0], a[1]), self, a) self.annotations.add(tmp_annot) self.addItem(tmp_annot) for a in self.annotation_manager.negative_annotations_rect[ self.image_name]: tmp_annot = AnnotationItem(QPointF(a[0], a[1]), self, a, annotation_negative=True) self.negative_annotations.add(tmp_annot) self.addItem(tmp_annot) for a in self.annotation_manager.roi_points[self.image_name]: tmp_annot = PolygonPointItem(self, (a[0], a[1])) self.roi_point_items.add(tmp_annot) self.addItem(tmp_annot) self.annotation_tool.draw_convex_hull() self.draw_roi_polygon() self.count_label.setText("Number of annotations: " + str(len(self.annotations))) self.update() def change_image(self, image_path, image_name): self.enabled = True self.clear() self.annotations = set() self.negative_annotations = set() self.roi_point_items = set() self.polygon = None self.roi_polygon = None self.removal_circle_item = None self.annotation_undo_stack = [] self.action_undo_stack = [] self.add_image(image_path) self.setSceneRect(self.itemsBoundingRect()) self.image_path = image_path self.annotation_manager.image_shapes[image_name] = [ self.image.width(), self.image.height() ] for a in self.annotation_manager.annotations_rect[image_name]: tmp_annot = AnnotationItem(QPointF(a[0], a[1]), self, a) self.annotations.add(tmp_annot) self.addItem(tmp_annot) for a in self.annotation_manager.negative_annotations_rect[image_name]: tmp_annot = AnnotationItem(QPointF(a[0], a[1]), self, a, annotation_negative=True) self.negative_annotations.add(tmp_annot) self.addItem(tmp_annot) for a in self.annotation_manager.roi_points[image_name]: tmp_annot = PolygonPointItem(self, (a[0], a[1])) self.roi_point_items.add(tmp_annot) self.addItem(tmp_annot) self.image_name = image_name self.count_label.setText("Number of annotations: " + str(len(self.annotations))) self.annotation_tool.draw_convex_hull() self.draw_roi_polygon() self.update() def get_annotations(self): return self.annotations def draw_roi_polygon(self): if len(self.annotation_manager.roi_points[self.image_name]) > 2: if self.roi_polygon != None: self.removeItem(self.roi_polygon) self.roi_polygon = None brush = QBrush(QColor("#fc8803")) pen = QPen(brush, 5) polygon = QPolygonF([ QPointF(x[0], x[1]) for x in np.array( self.annotation_manager.roi_points[self.image_name]) ]) self.roi_polygon = QGraphicsPolygonItem(polygon) self.roi_polygon.setPen(pen) self.addItem(self.roi_polygon) else: if self.roi_polygon != None: self.removeItem(self.roi_polygon) self.roi_polygon = None def keyPressEvent(self, QKeyEvent): # Changes the cursor icon when deleting if self.picked_tool == 0 or self.picked_tool == 2: if QKeyEvent.key( ) == Qt.Key_Control and not self.currently_deleting: self.currently_deleting = True QApplication.setOverrideCursor(QCursor(Qt.CrossCursor)) def keyReleaseEvent(self, QKeyEvent): # Changes the cursor icon back to normal when deleting stops if self.picked_tool == 0 or self.picked_tool == 2: if QKeyEvent.key() == Qt.Key_Control and self.currently_deleting: self.currently_deleting = False QApplication.setOverrideCursor(QCursor(Qt.ArrowCursor)) def handle_remove_action(self, pos): num_removed = self.removal_tool.remove_action(pos) if num_removed > 0: self.annotation_manager.annotations_changed[self.image_name] = True self.count_label.setText("Number of annotations: " + str(len(self.annotations))) self.annotation_tool.draw_convex_hull() self.saved = False def handle_undo_remove_action(self): removed = self.removal_tool.undo_remove_action() if removed: self.annotation_manager.annotations_changed[self.image_name] = True self.count_label.setText("Number of annotations: " + str(len(self.annotations))) self.annotation_tool.draw_convex_hull() self.saved = False def handle_undo_annotation_action(self): removed = self.annotation_tool.undo_annotation() if removed: self.annotation_manager.annotations_changed[self.image_name] = True self.count_label.setText("Number of annotations: " + str(len(self.annotations))) self.saved = False self.annotation_tool.draw_convex_hull() def handle_undo_action(self): print("Test") if len(self.action_undo_stack) > 0: tool_used = self.action_undo_stack.pop() if tool_used == 0 or tool_used == 2: print("Undo for annotations") self.handle_undo_annotation_action() elif tool_used == 1: print("Undo remove") self.handle_undo_remove_action() def mousePressEvent(self, QMouseEvent): super(GraphicsScene, self).mousePressEvent(QMouseEvent) # if left mouse button pos = QMouseEvent.scenePos() modifiers = QApplication.keyboardModifiers() # if tool is annotation adding if self.picked_tool == 0 or self.picked_tool == 2: if (QMouseEvent.button() == 1): self.currently_drawing = True self.annotation_tool.add_progress_annotation( pos, [pos.x(), pos.y(), pos.x() + 1, pos.y() + 1]) self.start_x = pos.x() self.start_y = pos.y() elif self.picked_tool == 1 and QMouseEvent.buttons() == Qt.LeftButton: self.handle_remove_action(pos) elif self.picked_tool == 3 and QMouseEvent.button( ) == 1 and modifiers != Qt.ControlModifier: print("poly tool pressed") tmp_annot = PolygonPointItem(self, (pos.x(), pos.y())) self.currently_drawing = False self.addItem(tmp_annot) self.roi_point_items.add(tmp_annot) self.annotation_manager.roi_points[self.image_name].append( (pos.x(), pos.y())) self.draw_roi_polygon() self.update() def mouseMoveEvent(self, QMouseEvent): # pos = QPointF(QMouseEvent.pos()) super(GraphicsScene, self).mouseMoveEvent(QMouseEvent) pos = QMouseEvent.scenePos() modifiers = QApplication.keyboardModifiers() # if tool is annotation adding if self.picked_tool == 0 or self.picked_tool == 2 or self.picked_tool == 3: if self.currently_drawing: self.end_x = pos.x() self.end_y = pos.y() x1 = self.end_x y1 = self.end_y x2 = self.start_x y2 = self.start_y if self.picked_tool == 3 or self.picked_tool == 2: self.annotation_tool.add_progress_annotation( pos, [x1, y1, x2, y2], rectangular=True) else: self.annotation_tool.add_progress_annotation( pos, [x1, y1, x2, y2]) elif self.picked_tool == 1 and QMouseEvent.buttons() == Qt.LeftButton: self.handle_remove_action(pos) if self.picked_tool == 1 or self.picked_tool == 4: self.removal_tool.draw_removal_circle(pos) self.update() def mouseReleaseEvent(self, QMouseEvent): pos = QMouseEvent.scenePos() # Handle adding positive/negative anotations if self.picked_tool == 0 or self.picked_tool == 2: if (QMouseEvent.button() == 1): self.currently_drawing = False self.end_x = pos.x() self.end_y = pos.y() # add annotation if (abs(self.start_x - self.end_x) > 1 and abs(self.start_y - self.end_y) > 1): self.annotation_tool.add_annotation( pos, [self.start_x, self.start_y, self.end_x, self.end_y]) # remove in progress annotation self.annotation_tool.remove_progress_annotation() # Save remove action to undo stack elif self.picked_tool == 1 and QMouseEvent.button() == 1: removed = self.removal_tool.end_of_action() if removed: self.action_undo_stack.append(self.picked_tool) self.update() def drawRect(self, event, qp, rect): qp.setPen(QColor(168, 34, 3)) qp.setFont(QFont('Decorative', 10)) qp.drawRect(*rect) self.update()