def update_path_draw2Pt_bk(self): p = QPainterPath() p.moveTo(self.pos1) if len(self.connPoints) == 0: y = self.pos2.y() pt =QPointF(self.pos1.x(),y) else: pt_next = self.connPoints[0] dx = np.abs(self.pos1.x()-pt_next.x()) dy = np.abs(self.pos1.y()-pt_next.y()) if dx > dy: pt = QPointF(self.pos1.x(),pt_next.y()) else: pt = QPointF(pt_next.x(),self.pos1.y()) if len(self.connPoints)==1: pt_1next = self.pos1; else: pt_1next = self.connPoints[1] if pt.x() == pt_next.x() == pt_1next.x() and \ (pt_next.y() < pt.y() < pt_1next.y() or pt_1next.y() < pt.y() < pt_next.y()): self.connPoints.remove(pt_next) elif pt.y() == pt_next.y() == pt_1next.y() and \ (pt_next.x() < pt.x() < pt_1next.x() or pt_1next.x() < pt.x() < pt_next.x()): self.connPoints.remove(pt_next) p.lineTo(pt) for el in self.connPoints: p.lineTo(el) p.lineTo(self.pos2) self.setPath(p)
def calcConnector(x1, y1, dir1, rect1, x2, y2, dir2, rect2): lines = [] if dir2 == "down": if y1 > y2 and x1 == x2: #direct connection lines.append(QLineF(QPointF(x1, y1), QPointF(x2, y2))) elif y1 > (y2 + 2 * self.grid): #z-connection return zLine(x1, y1, x2, y2, 'H') elif (rect2.topLeft().x() > (rect1.topRight().x() + 2 * grid) or rect1.topLeft().x() > (rect2.topRight().x() + 2 * grid)): #enough place to draw a line between both rects p1 = QPointF(x1, y2 + 2 * grid) p2 = QPointF(x2, y2 - 2 * grid) lines.append(QLineF(QPointF(x1, y1), p1)) lines += zLine(p1.x(), p1.y(), p2.x(), p2.y(), 'V') lines.append(QLineF(QPointF(x2, y2), p2)) else: #round both start = QPointF(x1, y1 - 2 * grid) end = QPointF(x2, y2 + 2 * grid) line.append(QLineF(start, QPointF(x1, y1))) if x1 > x2: length = x1 - rect2.topLeft.x() + 2 * grid lines += uLine(start, end, "right") else: length = rect2.topRight.x() - x1 + 2 * grid lines += uLine(start, end, "left") line.append(QLineF(end, QPointF(x2, y2)))
def __calculateEndingPoints_topToBottom(self): """ Private method to calculate the ending points of the association item. The ending points are calculated from the top center of the lower item to the bottom center of the upper item. """ if self.itemA is None or self.itemB is None: return self.prepareGeometryChange() rectA = self.__mapRectFromItem(self.itemA) rectB = self.__mapRectFromItem(self.itemB) midA = QPointF(rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height() / 2.0) midB = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height() / 2.0) if midA.y() > midB.y(): startP = QPointF(rectA.x() + rectA.width() / 2.0, rectA.y()) endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height()) else: startP = QPointF(rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height()) endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y()) self.setPoints(startP.x(), startP.y(), endP.x(), endP.y())
def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1)
class RSegment(QObject): def __init__(self, x1, y1, x2, y2, color, line_width): self._x1 = x1 self._y1 = y1 self._x2 = x2 self._y2 = y2 self._pos = QPointF(x1, y1) super().__init__() self.line = QGraphicsLineItem() self.line.setLine(x1, y1, x2, y2) pen = QPen() pen.setWidthF(line_width) pen.setColor(color) self.line.setPen(pen) def x(self): return self._pos.x() def y(self): return self._pos.y() @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._x1 = self._x1 + delta_x self._y1 = self._y1 + delta_y self._x2 = self._x2 + delta_x self._y2 = self._y2 + delta_y self.line.setLine(self._x1, self._y1, self._x2, self._y2) self._pos = value
def done_work(result): result, error = result if error: raise error img_bbox: QRectF = self.viewer.pixmap.sceneBoundingRect() offset = QPointF(img_bbox.width() / 2, img_bbox.height() / 2) for entry in result: try: vo: AnnotaVO = entry points = map(float, vo.points.split(",")) points = list(more_itertools.chunked(points, 2)) if vo.kind == "box": x = points[0][0] - offset.x() y = points[0][1] - offset.y() w = math.fabs(points[0][0] - points[1][0]) h = math.fabs(points[0][1] - points[1][1]) roi: QRectF = QRectF(x, y, w, h) rect = EditableBox(roi) rect.label = vo.label self.viewer.scene().addItem(rect) elif vo.kind == "polygon": polygon = EditablePolygon() polygon.label = vo.label self.viewer.scene().addItem(polygon) for p in points: polygon.addPoint( QPoint(p[0] - offset.x(), p[1] - offset.y())) except Exception as ex: print(ex)
def update_path_draw2Pt(self): p = QPainterPath() p.moveTo(self.pos1) for el in self.connPoints: p.lineTo(el) if len(self.connPoints) == 0: y = self.pos1.y() pt = QPointF(self.pos2.x(), y) else: pt_prev = self.connPoints[-1] dx = np.abs(self.pos2.x() - pt_prev.x()) dy = np.abs(self.pos2.y() - pt_prev.y()) if dx > dy: pt = QPointF(self.pos2.x(), pt_prev.y()) else: pt = QPointF(pt_prev.x(), self.pos2.y()) if len(self.connPoints) == 1: pt_2prev = self.pos1 else: pt_2prev = self.connPoints[-2] if pt.x() == pt_prev.x() == pt_2prev.x() and \ (pt_prev.y() < pt.y() < pt_2prev.y() or pt_2prev.y() < pt.y() < pt_prev.y()): self.connPoints.remove(pt_prev) elif pt.y() == pt_prev.y() == pt_2prev.y() and \ (pt_prev.x() < pt.x() < pt_2prev.x() or pt_2prev.x() < pt.x() < pt_prev.x()): self.connPoints.remove(pt_prev) p.lineTo(pt) p.lineTo(self.pos2) self.setPath(p)
def save_annotations(self): scene: QGraphicsScene = self.viewer.scene() self._annot_dao.delete(self.source.id) annot_list = [] for item in scene.items(): img_bbox: QRectF = self.viewer.pixmap.sceneBoundingRect() offset = QPointF(img_bbox.width() / 2, img_bbox.height() / 2) if isinstance(item, EditableBox): item_box: QRectF = item.sceneBoundingRect() x1 = math.floor(item_box.topLeft().x() + offset.x()) y1 = math.floor(item_box.topRight().y() + offset.y()) x2 = math.floor(item_box.bottomRight().x() + offset.x()) y2 = math.floor(item_box.bottomRight().y() + offset.y()) box = AnnotaVO() box.label = item.label.id if item.label else None box.entry = self.source.id box.kind = "box" box.points = ",".join(map(str, [x1, y1, x2, y2])) annot_list.append(box) elif isinstance(item, EditablePolygon): points = [[ math.floor(pt.x() + offset.x()), math.floor(pt.y() + offset.y()) ] for pt in item.points] points = np.asarray(points).flatten().tolist() poly = AnnotaVO() poly.label = item.label.id if item.label else None poly.entry = self.source.id poly.kind = "polygon" poly.points = ",".join(map(str, points)) annot_list.append(poly) self._annot_dao.save(annot_list)
def itemChange(self, change, value): """ move position of grabber after resize""" if change == QGraphicsItem.ItemPositionChange and self.isEnabled(): p = QPointF(self.pos()) if self.direction == Qt.Horizontal: p.setX(value.x()) if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: points = self.parentItem().refLine.points point1 = points[self.parentItem().refIndex] point2 = points[self.parentItem().refIndex + 1] point1 = self.parentItem().mapFromItem(self.parentItem().refLine, point1) point2 = self.parentItem().mapFromItem(self.parentItem().refLine, point2) if p.x() < min(point1.x(), point2.x()): p.setX(min(point1.x(), point2.x())) elif p.x() > max(point1.x(), point2.x()): p.setX(max(point1.x(), point2.x())) elif self.direction == Qt.Vertical: p.setY(value.y()) if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: points = self.parentItem().refLine.points point1 = points[self.parentItem().refIndex] point2 = points[self.parentItem().refIndex + 1] point1 = self.parentItem().mapFromItem(self.parentItem().refLine, point1) point2 = self.parentItem().mapFromItem(self.parentItem().refLine, point2) if p.y() < min(point1.y(), point2.y()): p.setY(min(point1.y(), point2.y())) elif p.y() > max(point1.y(), point2.y()): p.setY(max(point1.y(), point2.y())) movement = p - self.pos() self.m_annotation_item.movePoints(self.m_index, movement) return p return super(Grabber, self).itemChange(change, value)
def itemChange(self, change, value): # if position of label changes if change == QGraphicsItem.ItemPositionChange and self.scene(): newPos = QPointF(value) # new position newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) # pos of center points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] if firstPoint.x() == endPoint.x(): if min(firstPoint.y(), endPoint.y()) > newPos.y(): newPos.setY(min(firstPoint.y(), endPoint.y())) elif newPos.y() > max(firstPoint.y(), endPoint.y()): newPos.setY(max(firstPoint.y(), endPoint.y())) elif firstPoint.y() == endPoint.y(): if min(firstPoint.x(), endPoint.x()) > newPos.x(): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) # change center pos to top-left return newPos if change == QGraphicsItem.ItemPositionHasChanged and self.scene(): self.updateGap() self.updateLine() return return super(LineLabel, self).itemChange(change, value)
def reconfigureRect(self, top_left, bottom_right, padding=80, do_grid=False): """Summary 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) if do_grid: self.griditem.updateGrid() return (ptTL.x(), ptTL.y()), (ptBR.x(), ptBR.y())
def intersectionOfSegmentAndCircle(self, center: QtCore.QPointF, radius: float, p1: QtCore.QPointF, p2: QtCore.QPointF): x0, y0 = center.x(), center.y() x1, y1 = p1.x(), p1.y() x2, y2 = p2.x(), p2.y() r = radius intercect1 = ((x0 - x2) * (x1 - x2) + (y0 - y2) * (y1 - y2) - (r**2 * ((x1 - x2)**2 + (y1 - y2)**2) - (-(x2 * y0) - x0 * y1 + x2 * y1 + x1 * (y0 - y2) + x0 * y2)**2)**.5) / ((x1 - x2)**2 + (y1 - y2)**2) intercect2 = ((x0 - x2) * (x1 - x2) + (y0 - y2) * (y1 - y2) + (r**2 * ((x1 - x2)**2 + (y1 - y2)**2) - (-(x2 * y0) - x0 * y1 + x2 * y1 + x1 * (y0 - y2) + x0 * y2)**2)**.5) / ((x1 - x2)**2 + (y1 - y2)**2) if isclose(intercect1, 1., abs_tol=1e-5) or isclose( intercect1, 0., abs_tol=1e-5): intercect = intercect2 else: intercect = intercect1 # intercect = max([intercect1, intercect2]) # if abs(intercect) > 1.: # print("Warning!") # intercect1 = (p1 + p2) * intercect1 # intercect2 = (p1 + p2) * intercect2 intercect = (p1 * intercect + (1. - intercect) * p2) return intercect
def get_angle(self,A,B,dI): C = [(A.x()+B.x())/2,(A.y()+B.y())/2] I = QPointF(C[0]+dI[0],C[1]+dI[1]) # AI = [I[0]-A.x(),I[1]-A.y()] # BI = [I[0]-B.x(),I[1]-B.y()] # AB = [B.x()-A.x(),B.y()-A.y()] # # ##angle(AB = [1,0],AB) # if math.sqrt(AB[0]**2+AB[1]**2) != 0: # alpha = math.acos(AB[0]/math.sqrt(AB[0]**2+AB[1]**2)) # alpha = int(self.to_deg(angles[0])) # # ##angle(AC = [1,0],AI) # if math.sqrt(AI[0]**2+AI[1]**2) != 0: # angles[0] = math.acos(AI[0]/math.sqrt(AI[0]**2+AI[1]**2)) # angles[0] = int(self.to_deg(angles[0])) # # # ##angle(BC,BI) # if math.sqrt(BI[0]**2+BI[1]**2) != 0: # angles[1] = math.acos(BI[0]/math.sqrt(BI[0]**2+BI[1]**2)) # angles[1] = int(self.to_deg(angles[1])) alpha = QLineF(A.x(), A.y(),I.x(), I.y()).angle() beta = QLineF(I.x(), I.y(),B.x(), B.y()).angle() angles = [-alpha,180-beta] return angles
def itemChange(self, change, value): # self.logger.debug(change, value) # Snap grid excludes alignment if change == self.ItemPositionChange: if self.parent.parent().snapGrid: snapSize = self.parent.parent().snapSize self.logger.debug("itemchange") self.logger.debug(type(value)) value = QPointF(value.x() - value.x() % snapSize, value.y() - value.y() % snapSize) return value else: # if self.hasElementsInYBand() and not self.elementInY() and not self.aligned: if self.parent.parent().alignMode: if self.hasElementsInYBand(): return self.alignBlock(value) else: # self.aligned = False return value else: return value else: return super(BlockItem, self).itemChange(change, value)
def mouseIsOnIO(self, mousePos, click = False): #Returns the IO that the mouse is on for i in range(0, len(self.ioList)): #Adjust if IO is centered on a side if self.ioList[i][3] == 'left': yTranslation = self.yTranslationLeftIO else: yTranslation = self.yTranslationRightIO #Get point of IO IOPoint = QPointF(self.ioList[i][0], self.ioList[i][1] + yTranslation) #If mouse is over IO -> return IO if mousePos.x() > IOPoint.x() and mousePos.x() < IOPoint.x() + self.ioWidth: if mousePos.y() > IOPoint.y() and mousePos.y() < IOPoint.y() + self.ioHeight: # entry point for drawing graphs....... # if click: # print('mouse on IO: ' + str(i) + ' (' + str(self.ioList[i][3]) + ', ' + str(self.ioList[i][4]) + ')') #Update the hover paramater of the IO self.ioList.insert(i, (self.ioList[i][0], self.ioList[i][1], self.ioList[i][2], self.ioList[i][3], self.ioList[i][4], True, self.ioList[i][6])) del self.ioList[i + 1] self.setFlag(QGraphicsItem.ItemIsSelectable, False) self.setFlag(QGraphicsItem.ItemIsMovable, False) self.hover = False return i #If no IO is found under the mouse -> make sure hovering is enabled and return -1 self.hover = True self.setHoveringToFalse() return -1
def update_path_draw2OutPort(self): p = QPainterPath() p.moveTo(self.pos1) if len(self.connPoints) == 0: pt1 = QPointF((self.pos1.x()+self.pos2.x())/2, self.pos1.y()) pt2 = QPointF((self.pos1.x()+self.pos2.x())/2, self.pos2.y()) p.lineTo(pt1) p.lineTo(pt2) else: pt= self.connPoints[0] pt1 = QPointF((self.pos1.x()+pt.x())/2, self.pos1.y()) pt2 = QPointF((self.pos1.x()+pt.x())/2, pt.y()) p.lineTo(pt1) p.lineTo(pt2) if len(self.connPoints)>=2: pt_2next = self.connPoints[1] if pt_2next.x() == pt1.x() == pt2.x() or \ pt_2next.y() == pt1.y() == pt2.y(): self.connPoints.remove(pt_2next) for el in self.connPoints: p.lineTo(el) p.lineTo(self.pos2) self.setPath(p)
def convex_check(figure): size = len(figure) vector2d = list() check_sign = 0 if size < 3: return False, check_sign for i in range(1, size): if i < size - 1: ab = QPointF(figure[i].x() - figure[i - 1].x(), figure[i].y() - figure[i - 1].y()) bc = QPointF(figure[i + 1].x() - figure[i].x(), figure[i + 1].y() - figure[i].y()) else: ab = QPointF(figure[i].x() - figure[i - 1].x(), figure[i].y() - figure[i - 1].y()) bc = QPointF(figure[1].x() - figure[0].x(), figure[1].y() - figure[0].y()) vector2d.append(ab.x() * bc.y() - ab.y() * bc.x()) exist_sign = False for i in range(len(vector2d)): if vector2d[i] == 0: continue if exist_sign: if sign(vector2d[i]) != check_sign: return False, check_sign else: check_sign = sign(vector2d[i]) exist_sign = True return True, check_sign
def _determineStartLabelHeight(self, start_point: QtCore.QPointF, down: bool, startAtThis: bool): """ 2020.09.28新增。决定开始标签高度。 如果启用重叠避免,则需要查找近邻计算高度,同时维护数据; 如果不启用,直接返回配置项。 """ if not self.graph.UIConfigData()['avoid_cover']: return self.graph.UIConfigData()['start_label_height'] # 启用重叠避免的情况 thres = 100 # 左右超过这个范围,就不再搜 lst = self.labelSpans.setdefault((start_point.y(), down), []) a = bisect.bisect_left(lst, (start_point.x() - thres, )) b = bisect.bisect_right(lst, (start_point.x() + thres, )) w = self.spanItemWidth # 当前标签宽度 h0 = self.spanItemWidth if startAtThis: wl, wr = w / 2, w / 2 else: wl, wr = w, 0 h = self.graph.UIConfigData()['base_label_height'] occupied_heights = [] for i in range(a, b): x, hi, wli, wri = lst[i] if 0 <= start_point.x() - x < wri + wl or 0 <= x - start_point.x( ) < wli + wr: # 确定冲突 occupied_heights.append(hi) while h in occupied_heights: h += self.graph.UIConfigData()['step_label_height'] tpl = (start_point.x(), h, wl, wr) bisect.insort(lst, tpl) self.startLabelInfo = ((start_point.y(), down), tpl) return h
def mousePressEvent(self, event): position = QPointF(event.scenePos()) print("pressed here: " + str(int(position.x() / 70)) + ", " + str(int(position.y() / 70))) self.boardObject.addBlock(int(position.x() / 70), int(position.y() / 70)) self.boardObject.getArray()
def __init__(self, p1: QPointF, p2: QPointF = None): if p2 is not None: self.x = p2.x() - p1.x() self.y = p2.y() - p1.y() else: self.x = p1.x() self.y = p1.y()
def hoverEnterEvent(self, event): """ """ self.prepareGeometryChange() self._hover = True self._pen.setWidth(self.PEN_WIDTH_HOVER) self._penMargin = self.PEN_WIDTH_HOVER / 2 a = QPointF(-self._WIDTH / 2 - randint(0, int(self._HEIGHT / 3)), -self._HEIGHT / 2 - randint(0, int(self._HEIGHT / 3))) b = QPointF(self._WIDTH / 2 + randint(0, int(self._HEIGHT / 3)), -self._HEIGHT / 2 - randint(0, int(self._HEIGHT / 3))) c = QPointF(self._WIDTH / 2 + randint(0, int(self._HEIGHT / 3)), self._HEIGHT / 2 + randint(0, int(self._HEIGHT / 3))) d = QPointF(-self._WIDTH / 2 - randint(0, int(self._HEIGHT / 3)), self._HEIGHT / 2 + randint(0, int(self._HEIGHT / 3))) self._boundingRect = QRectF( QPointF( min(a.x(), d.x()) - self._penMargin, min(a.y(), b.y()) - self._penMargin), QPointF( max(b.x(), c.x()) + self._penMargin, max(c.y(), d.y()) + self._penMargin)) self._shape = QPainterPath() self._shape.moveTo(a) self._shape.lineTo(b) self._shape.lineTo(c) self._shape.lineTo(d) self._shape.lineTo(a) super().hoverEnterEvent(event)
def rotatePointClockWise(self, p: QPointF, angle): ''' Standard 2x2 rotation matrix, counter-clockwise | cos(phi) sin(phi) | | -sin(phi) cos(phi) | ''' p.setX(cos(angle) * p.x() + sin(angle)* p.y()) p.setY((-1.0 * sin(angle) * p.x()) + cos(angle) * p.y())
def isClockwise(self, a: QPointF, b: QPointF, c: QPointF): val = (b.y() - a.y()) * (c.x() - b.x()) - (c.y() - b.y()) * (b.x() - a.x()) if (val == 0): return 0 if (val > 0): return 1 return -1
def buildEdgeFromGenericEdge(self, item, element): """ Build an edge using the given item type and QDomElement. :type item: Item :type element: QDomElement :rtype: AbstractEdge """ data = element.firstChildElement('data') while not data.isNull(): if data.attribute('key', '') == self.keys['edge_key']: points = [] polyLineEdge = data.firstChildElement('y:PolyLineEdge') path = polyLineEdge.firstChildElement('y:Path') collection = path.elementsByTagName('y:Point') for i in range(0, collection.count()): point = collection.at(i).toElement() pos = QPointF(float(point.attribute('x')), float(point.attribute('y'))) pos = QPointF(snapF(pos.x(), DiagramScene.GridSize), snapF(pos.y(), DiagramScene.GridSize)) points.append(pos) kwargs = { 'id': element.attribute('id'), 'source': self.scene.node(element.attribute('source')), 'target': self.scene.node(element.attribute('target')), 'breakpoints': points, } edge = self.factory.create(item, self.scene, **kwargs) # yEd, differently from the node pos whose origin matches the TOP-LEFT corner, # consider the center of the shape as original anchor point (0,0). So if the # anchor point hs a negative X it's moved a bit on the left with respect to # the center of the shape (the same applies for the Y axis) sourceP = QPointF(float(path.attribute('sx')), float(path.attribute('sy'))) sourceP = edge.source.pos() + sourceP sourceP = QPointF(snapF(sourceP.x(), DiagramScene.GridSize), snapF(sourceP.y(), DiagramScene.GridSize)) targetP = QPointF(float(path.attribute('tx')), float(path.attribute('ty'))) targetP = edge.target.pos() + targetP targetP = QPointF(snapF(targetP.x(), DiagramScene.GridSize), snapF(targetP.y(), DiagramScene.GridSize)) painterPath = edge.source.painterPath() if painterPath.contains(edge.source.mapFromScene(sourceP)): edge.source.setAnchor(edge, sourceP) painterPath = edge.target.painterPath() if painterPath.contains(edge.target.mapFromScene(targetP)): edge.target.setAnchor(edge, targetP) edge.source.addEdge(edge) edge.target.addEdge(edge) return edge data = data.nextSiblingElement('data') return None
class PolygonBase(QGraphicsWidget): def __init__(self): super().__init__() self.MAR = 50 self.points = [] self.top_left = QPointF(float('Inf'), float('Inf')) self.bottom_right = QPointF(-float('Inf'), -float('Inf')) self.rect = QRectF() def boundingRect(self): return self.rect def update_bounding_rect(self, pt): """插入新顶点时,更新 bounding rect Args: pt (QPointF): 新插入的顶点 """ self.top_left = QPointF(min(self.top_left.x(), pt.x()), min(self.top_left.y(), pt.y())) self.bottom_right = QPointF(max(self.bottom_right.x(), pt.x()), max(self.bottom_right.y(), pt.y())) self.rect = QRectF(self.top_left, self.bottom_right).adjusted(-self.MAR, -self.MAR, self.MAR, self.MAR) self.prepareGeometryChange() def move_bounding_rect(self, offset): """移动多边形时,更新 bounding rect Args: offset (QPointF): 平移向量 """ self.top_left += offset self.bottom_right += offset self.rect.adjust(offset.x(), offset.y(), offset.x(), offset.y()) self.prepareGeometryChange() def get_points(self): """获取多边形中的顶点列表 Returns: points (list[QPointF]): 顶点列表 """ return self.points def get_vertices(self): """获取多边形中的顶点列表 Returns: vertices (list[list[float]]): 顶点列表 """ vertices = [[vertex.x(), vertex.y()] for vertex in self.points] return vertices
def mouseMovedWhileCreatingObject(self, pos, modifiers): renderer = self.mapDocument().renderer() pixelCoords = renderer.screenToPixelCoords_(pos) # Update the size of the new map object objectPos = self.mNewMapObjectItem.mapObject().position() newSize = QPointF(max(0.0, pixelCoords.x() - objectPos.x()), max(0.0, pixelCoords.y() - objectPos.y())) # Holding shift creates circle or square if (modifiers & Qt.ShiftModifier): m = max(newSize.x(), newSize.y()) newSize.setX(m) newSize.setY(m) SnapHelper(renderer, modifiers).snap(newSize) self.mNewMapObjectItem.resizeObject(QSizeF(newSize.x(), newSize.y()))
def calcRowCol(self, point: QPointF): """ Calculate the network row and column that a point is int calc the row and column indexes of a point The following is the algorithm: 1. Find the distance between the point and the left (or top) 2. Divide the distance with the width of path to find the relative position 3. Multipile this relative position with the number of rows/cols 4. Convert the result to int to find the indexes 5. If the index is the number of row/col reduce the index (This is for the case the the point is on the boundary and in this case the relative position is 1 which will cause the indexes to be the number of rows/cols - out of the matrix indexes) Args: point (QPointF) : The point to resolve Returns: int : The network row that the point is in int : The network column that the point is in """ partialX = (point.x() - self.charPath.boundingRect().left()) / self.charPath.boundingRect().width() partialY = (point.y() - self.charPath.boundingRect().top()) / self.charPath.boundingRect().height() col_idx = int(partialX * self.netCols) row_idx = int(partialY * self.netRows) if row_idx == self.netRows: row_idx -= 1 if col_idx == self.netCols: col_idx -= 1 return row_idx, col_idx
def render(self, widget): painter = widget.painter if not painter: return previousRenderHint = painter.renderHints() painter.setRenderHints(previousRenderHint | QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(QColor(253, 242, 245)) painter.drawEllipse(QRectF(self.pos - self.toPointF(self.size/2), self.size)) mouseOffset = QPointF(widget.mousePosition) \ - self.pos \ - QPointF(widget.frameGeometry().topLeft()) ox, oy = mouseOffset.x(), mouseOffset.y() distance = math.sqrt(ox**2 + oy**2) if distance > self.eyesight_radius: ox *= self.eyesight_radius / distance oy *= self.eyesight_radius / distance px = self.pos.x() + ox/self.eyesight_radius * (self.size-self.pupil_size).width() / 2 py = self.pos.y() + oy/self.eyesight_radius * (self.size-self.pupil_size).height() / 2 pos = QPointF(px, py) painter.setBrush(Qt.black) painter.drawEllipse(QRectF(pos - self.toPointF(self.pupil_size/2), self.pupil_size)) painter.setRenderHints(previousRenderHint)
def buildNodeFromShapeNode(self, item, element): """ Build a node using the given item type and QDomElement. :type item: Item :type element: QDomElement :rtype: AbstractNode """ data = element.firstChildElement('data') while not data.isNull(): if data.attribute('key', '') == self.keys['node_key']: shapeNode = data.firstChildElement('y:ShapeNode') geometry = shapeNode.firstChildElement('y:Geometry') kwargs = { 'id': element.attribute('id'), 'height': float(geometry.attribute('height')), 'width': float(geometry.attribute('width')), } node = self.factory.create(item, self.scene, **kwargs) # yEd uses the TOP-LEFT corner as (0,0) coordinate => we need to translate our # position (0,0), which is instead at the center of the shape, so that the TOP-LEFT # corner of the shape in yEd matches the TOP-LEFT corner of the shape in Eddy. # Additionally we force-snap the position to the grid so that items say aligned. pos = QPointF(float(geometry.attribute('x')), float(geometry.attribute('y'))) pos = pos + QPointF(node.width() / 2, node.height() / 2) pos = QPointF(snapF(pos.x(), DiagramScene.GridSize), snapF(pos.y(), DiagramScene.GridSize)) node.setPos(pos) return node data = data.nextSiblingElement('data') return None
def image(cls, **kwargs): """ Returns an image suitable for the palette. :rtype: QPixmap """ # INITIALIZATION pixmap = QPixmap(kwargs['w'], kwargs['h']) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) # INITIALIZE EDGE LINE pp1 = QPointF(((kwargs['w'] - 52) / 2), kwargs['h'] / 2) pp2 = QPointF(((kwargs['w'] - 52) / 2) + 52 - 2, kwargs['h'] / 2) line = QLineF(pp1, pp2) # CALCULATE HEAD COORDINATES angle = radians(line.angle()) p1 = QPointF(line.p2().x() + 2, line.p2().y()) p2 = p1 - QPointF(sin(angle + M_PI / 3.0) * 8, cos(angle + M_PI / 3.0) * 8) p3 = p1 - QPointF(sin(angle + M_PI - M_PI / 3.0) * 8, cos(angle + M_PI - M_PI / 3.0) * 8) # INITIALIZE EDGE HEAD head = QPolygonF([p1, p2, p3]) # DRAW THE POLYGON painter.setRenderHint(QPainter.Antialiasing) painter.setPen(QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(line) # DRAW HEAD painter.setPen(QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.setBrush(QColor(0, 0, 0)) painter.drawPolygon(head) # DRAW THE TEXT ON TOP OF THE EDGE space = 2 if Platform.identify() is Platform.Darwin else 0 painter.setFont(Font('Arial', 9, Font.Light)) painter.drawText(pp1.x() + space, (kwargs['h'] / 2) - 4, 'instanceOf') return pixmap
def _get_selected_edge(self, pos: QPointF, transform: QTransform, horizontal_selection: bool): x1, x2 = self.x, self.x + self.width y1, y2 = self.y, self.y + self.height x, y = pos.x(), pos.y() spacing = 5 spacing /= transform.m11() if horizontal_selection else transform.m22() if horizontal_selection: x1a, x1b = x1 - spacing, x1 + spacing y1a, y1b = y1, y2 x2a, x2b = x2 - spacing, x2 + spacing y2a, y2b = y1, y2 else: x1a, x1b, x2a, x2b = x1, x2, x1, x2 y1a, y1b = min(y1 - spacing, y1 + spacing), max(y1 - spacing, y1 + spacing) y2a, y2b = min(y2 - spacing, y2 + spacing), max(y2 - spacing, y2 + spacing) if x1a < x < x1b and y1a < y < y1b: self.selected_edge = 0 return 0 if x2a < x < x2b and y2a < y < y2b: self.selected_edge = 1 return 1 self.selected_edge = None return None
def getX(self, pos: QPointF) -> float: """ Args: pos: Description Returns: ``x`` position """ return pos.x()
def is_in_roi(self, pos: QPointF): x1 = self.rect().x() x2 = x1 + self.rect().width() y1 = self.rect().y() y2 = y1 + self.rect().width() if x1 < pos.x() < x2 and y1 < pos.y() < y2: return True return False
def getLargerEdgePath(self): #Used to fill in the small areas on the edge #This makes it easier to select the edge yTranslation = 2 #Curve 1 beginPoint = QPointF(self.beginPoint.x(), self.beginPoint.y() + yTranslation) curvePoint1 = QPointF(self.curvePoint1.x()+4, self.curvePoint1.y() + yTranslation) curvePoint2 = QPointF(self.curvePoint2.x()+4, self.curvePoint2.y() + yTranslation) endPoint = QPointF(self.endPoint.x(), self.endPoint.y() + yTranslation) path = QPainterPath(beginPoint) point1 = QPointF(curvePoint1.x(), curvePoint1.y()) point2 = QPointF(curvePoint2.x(), curvePoint2.y()) path.cubicTo(point1, point2, endPoint) #Arrow arrowBeginPoint = QPointF(self.endPoint.x(), self.endPoint.y() + 4) path.lineTo(arrowBeginPoint) if self.endSide == 'right': path.lineTo(QPointF(self.endPoint.x() - 10, self.endPoint.y())) else: path.lineTo(QPointF(self.endPoint.x() + 10, self.endPoint.y())) path.lineTo(QPointF(self.endPoint.x(), self.endPoint.y() - 4)) path.lineTo(QPointF(self.endPoint.x(), self.endPoint.y() - 2)) #Curve 2 (back) endPoint = QPointF(self.beginPoint.x(), self.beginPoint.y() - yTranslation) curvePoint2 = QPointF(self.curvePoint1.x(), self.curvePoint1.y() - yTranslation) curvePoint1 = QPointF(self.curvePoint2.x(), self.curvePoint2.y() - yTranslation) beginPoint = QPointF(self.endPoint.x(), self.endPoint.y() - yTranslation) point1 = QPointF(curvePoint1.x(), curvePoint1.y()) point2 = QPointF(curvePoint2.x(), curvePoint2.y()) path.cubicTo(point1, point2, endPoint) if self.beginSide == 'right': path.lineTo(QPointF(self.beginPoint.x() - 10, self.beginPoint.y() - 2)) path.lineTo(QPointF(self.beginPoint.x() - 10, self.beginPoint.y() + 2)) else: path.lineTo(QPointF(self.beginPoint.x() + 10, self.beginPoint.y() - 2)) path.lineTo(QPointF(self.beginPoint.x() + 10, self.beginPoint.y() + 2)) path.lineTo(QPointF(self.beginPoint.x(), self.beginPoint.y() + 2)) return path
class Eye: pupil_size = QSizeF(5, 5) eyesight_radius = 100.0 def __init__(self, x, y, w, h): ## x, y are the coordinates of the center of the eye. ## w, h are the total width and height of the eye. self.size = QSizeF(w, h) self.pos = QPointF(x, y) def toPointF(self, size): return QPointF(size.width(), size.height()) def render(self, widget): painter = widget.painter if not painter: return previousRenderHint = painter.renderHints() painter.setRenderHints(previousRenderHint | QPainter.Antialiasing) painter.setPen(Qt.NoPen) painter.setBrush(QColor(253, 242, 245)) painter.drawEllipse(QRectF(self.pos - self.toPointF(self.size/2), self.size)) mouseOffset = QPointF(widget.mousePosition) \ - self.pos \ - QPointF(widget.frameGeometry().topLeft()) ox, oy = mouseOffset.x(), mouseOffset.y() distance = math.sqrt(ox**2 + oy**2) if distance > self.eyesight_radius: ox *= self.eyesight_radius / distance oy *= self.eyesight_radius / distance px = self.pos.x() + ox/self.eyesight_radius * (self.size-self.pupil_size).width() / 2 py = self.pos.y() + oy/self.eyesight_radius * (self.size-self.pupil_size).height() / 2 pos = QPointF(px, py) painter.setBrush(Qt.black) painter.drawEllipse(QRectF(pos - self.toPointF(self.pupil_size/2), self.pupil_size)) painter.setRenderHints(previousRenderHint)
def moveUIPoint(contour, point, delta): if point.segmentType is None: # point is an offCurve. Get its sibling onCurve and the other # offCurve. onCurve, otherPoint = _getOffCurveSiblingPoints(contour, point) # if the onCurve is selected, the offCurve will move along with it if onCurve.selected: return point.move(delta) if not onCurve.smooth: contour.dirty = True return # if the onCurve is smooth, we need to either... if otherPoint.segmentType is None and not otherPoint.selected: # keep the other offCurve inline line = QLineF(point.x, point.y, onCurve.x, onCurve.y) otherLine = QLineF( onCurve.x, onCurve.y, otherPoint.x, otherPoint.y) line.setLength(line.length() + otherLine.length()) otherPoint.x = line.x2() otherPoint.y = line.y2() else: # keep point in tangency with onCurve -> otherPoint segment, # ie. do an orthogonal projection line = QLineF(otherPoint.x, otherPoint.y, onCurve.x, onCurve.y) n = line.normalVector() n.translate(QPointF(point.x, point.y) - n.p1()) targetPoint = QPointF() n.intersect(line, targetPoint) # check that targetPoint is beyond its neighbor onCurve # we do this by calculating position of the offCurve and second # onCurve relative to the first onCurve. If there is no symmetry # in at least one of the axis, then we need to clamp onCurvePoint = line.p2() onDistance = line.p1() - onCurvePoint newDistance = targetPoint - onCurvePoint if (onDistance.x() >= 0) != (newDistance.x() <= 0) or \ (onDistance.y() >= 0) != (newDistance.y() <= 0): targetPoint = onCurvePoint # ok, now set pos point.x, point.y = targetPoint.x(), targetPoint.y() else: # point is an onCurve. Move its offCurves along with it. index = contour.index(point) point.move(delta) for d in (-1, 1): # edge-case: contour open, trailing offCurve and moving first # onCurve in contour if contour.open and index == 0 and d == -1: continue pt = contour.getPoint(index + d) if pt.segmentType is None: pt.move(delta) contour.dirty = True
def getEdgePath(self): yTranslation = 2 #Curve 1 beginPoint = QPointF(self.beginPoint.x(), self.beginPoint.y() + yTranslation) curvePoint1 = QPointF(self.curvePoint1.x(), self.curvePoint1.y() + yTranslation) curvePoint2 = QPointF(self.curvePoint2.x(), self.curvePoint2.y() + yTranslation) endPoint = QPointF(self.endPoint.x(), self.endPoint.y() + yTranslation) path = QPainterPath(beginPoint) point1 = QPointF(curvePoint1.x(), curvePoint1.y()) point2 = QPointF(curvePoint2.x(), curvePoint2.y()) path.cubicTo(point1, point2, endPoint) #Arrow arrowBeginPoint = QPointF(self.endPoint.x(), self.endPoint.y() + 4) path.lineTo(arrowBeginPoint) if self.endSide == 'right': path.lineTo(QPointF(self.endPoint.x() - 10, self.endPoint.y())) else: path.lineTo(QPointF(self.endPoint.x() + 10, self.endPoint.y())) path.lineTo(QPointF(self.endPoint.x(), self.endPoint.y() - 4)) path.lineTo(QPointF(self.endPoint.x(), self.endPoint.y() - 2)) #Curve 2 (back) endPoint = QPointF(self.beginPoint.x(), self.beginPoint.y() - yTranslation) curvePoint2 = QPointF(self.curvePoint1.x(), self.curvePoint1.y() - yTranslation) curvePoint1 = QPointF(self.curvePoint2.x(), self.curvePoint2.y() - yTranslation) beginPoint = QPointF(self.endPoint.x(), self.endPoint.y() - yTranslation) point1 = QPointF(curvePoint1.x(), curvePoint1.y()) point2 = QPointF(curvePoint2.x(), curvePoint2.y()) path.cubicTo(point1, point2, endPoint) if self.beginSide == 'right': path.lineTo(QPointF(self.beginPoint.x() - 10, self.beginPoint.y() - 2)) path.lineTo(QPointF(self.beginPoint.x() - 10, self.beginPoint.y() + 2)) else: path.lineTo(QPointF(self.beginPoint.x() + 10, self.beginPoint.y() - 2)) path.lineTo(QPointF(self.beginPoint.x() + 10, self.beginPoint.y() + 2)) path.lineTo(QPointF(self.beginPoint.x(), self.beginPoint.y() + 2)) return path
def getModelPos(self, pos: QPointF) -> Vec3T: """Y-axis is inverted in Qt +y === DOWN Args: pos: a position in this scene Returns: position in model coordinates """ sf = self.scale_factor x, y = pos.x()/sf, -1.0*pos.y()/sf return x, y, 0.
def helixIndex(self, point: QPointF) -> Vec2T: """Returns the (row, col) of the base which point lies within. Returns: point (tuple) in virtual_helix_item coordinates Args: point (TYPE): Description """ x = int(int(point.x()) / _BW) y = int(int(point.y()) / _BW) return (x, y)
def reconfigureRect(self, top_left: Vec2T, bottom_right: Vec2T, padding: int = 80 ) -> Tuple[Vec2T, Vec2T]: """Summary Args: top_left: top left corner point bottom_right: bottom right corner point padding: padding of the rectagle in pixels points Returns: 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())
def intersectLineGeometry(self, lineGeo, breakShape): """ Try to break lineGeo with the given breakShape. Will return the intersection points of lineGeo with breakShape. """ # TODO geos should be abs intersections = [] line = QLineF(lineGeo.Ps.x, lineGeo.Ps.y, lineGeo.Pe.x, lineGeo.Pe.y) for breakGeo in breakShape.geos.abs_iter(): if isinstance(breakGeo, LineGeo): breakLine = QLineF(breakGeo.Ps.x, breakGeo.Ps.y, breakGeo.Pe.x, breakGeo.Pe.y) intersection = QPointF(0, 0) # values do not matter res = line.intersect(breakLine, intersection) if res == QLineF.BoundedIntersection: intersections.append(Point(intersection.x(), intersection.y())) return intersections
def keyPressed(self, event): if (self.mAction != Action.NoAction): event.ignore() return moveBy = QPointF() x = event.key() if x==Qt.Key_Up: moveBy = QPointF(0, -1) elif x==Qt.Key_Down: moveBy = QPointF(0, 1) elif x==Qt.Key_Left: moveBy = QPointF(-1, 0) elif x==Qt.Key_Right: moveBy = QPointF(1, 0) else: super().keyPressed(event) return items = self.mapScene().selectedObjectItems() modifiers = event.modifiers() if (moveBy.isNull() or items.isEmpty() or (modifiers & Qt.ControlModifier)): event.ignore() return moveFast = modifiers & Qt.ShiftModifier snapToFineGrid = preferences.Preferences.instance().snapToFineGrid() if (moveFast): # TODO: This only makes sense for orthogonal maps moveBy.setX(moveBy.x() * self.mapDocument().map().tileWidth()) moveBy.setX(moveBy.y() * self.mapDocument().map().tileHeight()) if (snapToFineGrid): moveBy /= preferences.Preferences.instance().gridFine() undoStack = self.mapDocument().undoStack() undoStack.beginMacro(self.tr("Move %n Object(s)", "", items.size())) i = 0 for objectItem in items: object = objectItem.mapObject() oldPos = object.position() newPos = oldPos + moveBy undoStack.push(MoveMapObject(self.mapDocument(), object, newPos, oldPos)) i += 1 undoStack.endMacro()
def animate(self): self.angle += (math.pi / 30) xs = 200 * math.sin(self.angle) - 40 + 25 ys = 200 * math.cos(self.angle) - 40 + 25 self.m_lightSource.setPos(xs, ys) for item in self.m_items: effect = item.graphicsEffect() delta = QPointF(item.x() - xs, item.y() - ys) effect.setOffset(QPointF(delta.toPoint() / 30)) dd = math.hypot(delta.x(), delta.y()) color = effect.color() color.setAlphaF(max(0.4, min(1 - dd / 200.0, 0.7))) effect.setColor(color) self.m_scene.update()
def zoom(self, step, anchor="center"): """ Zooms the view by *step* increments (with a scale factor of 1.2^*step*), anchored to *anchor*: - QPoint_: center on that point - "cursor": center on the mouse cursor position - "center": center on the viewport - None: don’t anchor, i.e. stick to the viewport’s top-left. # TODO: improve docs from QGraphicsView descriptions. The default is "center". .. _QPoint: http://doc.qt.io/qt-5/qpoint.html """ oldScale = self._scale newScale = self._scale * pow(1.2, step) scrollArea = self._scrollArea if newScale < 1e-2 or newScale > 1e3: return if scrollArea is not None: # compute new scrollbar position # http://stackoverflow.com/a/32269574/2037879 hSB = scrollArea.horizontalScrollBar() vSB = scrollArea.verticalScrollBar() viewport = scrollArea.viewport() if isinstance(anchor, QPoint): pos = anchor elif anchor == "cursor": pos = self.mapFromGlobal(QCursor.pos()) elif anchor == "center": pos = self.mapFromParent( QPoint(viewport.width() / 2, viewport.height() / 2)) else: raise ValueError("invalid anchor value: {}".format(anchor)) scrollBarPos = QPointF(hSB.value(), vSB.value()) deltaToPos = pos / oldScale delta = deltaToPos * (newScale - oldScale) self.setScale(newScale) self.update() if scrollArea is not None: hSB.setValue(scrollBarPos.x() + delta.x()) vSB.setValue(scrollBarPos.y() + delta.y())
def wheelEvent(self, event): if event.modifiers() & Qt.ControlModifier: factor = pow(1.2, event.angleDelta().y() / 120.0) pos = event.pos() # compute new scrollbar position # http://stackoverflow.com/a/32269574/2037879 oldScale = self._scale newScale = self._scale * factor hSB = self._scrollArea.horizontalScrollBar() vSB = self._scrollArea.verticalScrollBar() scrollBarPos = QPointF(hSB.value(), vSB.value()) deltaToPos = (self.mapToParent(pos) - self.pos()) / oldScale delta = deltaToPos * (newScale - oldScale) # TODO: maybe put out a func that does multiply by default self.setScale(newScale) # TODO: maybe merge this in setScale self.adjustSize() self.update() hSB.setValue(scrollBarPos.x() + delta.x()) vSB.setValue(scrollBarPos.y() + delta.y()) event.accept() else: super().wheelEvent(event)
def baseAtPoint(self, pos: QPointF) -> Tuple[StrandSetT, int]: """Returns the (Strandset, index) under the location x, y or None. It shouldn't be possible to click outside a pathhelix and still call this function. However, this sometimes happens if you click exactly on the top or bottom edge, resulting in a negative y value. Args: pos: Description """ x, y = pos.x(), pos.y() part = self._model_part id_num = self._id_num base_idx = int(floor(x / _BASE_WIDTH)) min_base, max_base = 0, part.maxBaseIdx(id_num) if base_idx < min_base or base_idx >= max_base: base_idx = util.clamp(base_idx, min_base, max_base) if y < 0: y = 0 # HACK: zero out y due to erroneous click strand_type = floor(y * 1. / _BASE_WIDTH) # 0 for fwd, 1 for rev strand_type = int(util.clamp(strand_type, 0, 1)) strand_set = part.getStrandSets(id_num)[strand_type] return (strand_set, base_idx)
def offsetObjects(self, offset, bounds, wrapX, wrapY): for object in self.mObjects: objectCenter = object.bounds().center() if (not bounds.contains(objectCenter)): continue newCenter = QPointF(objectCenter + offset) if (wrapX and bounds.width() > 0): nx = math.fmod(newCenter.x() - bounds.left(), bounds.width()) if nx < 0: x = bounds.width() + nx else: x = nx newCenter.setX(bounds.left() + x) if (wrapY and bounds.height() > 0): ny = math.fmod(newCenter.y() - bounds.top(), bounds.height()) if ny < 0: x = bounds.height() + ny else: x = ny newCenter.setY(bounds.top() + x) object.setPosition(object.position() + (newCenter - objectCenter))
def paintEvent(self, evt): """ Protected method handling a paint event. @param evt reference to the paint event (QPaintEvent) """ painter = QPainter(self) if self.__image is not None and not self.__image.isNull(): x = (self.width() - self.__image.width()) // 2 - 1 y = (self.height() - self.__image.height()) // 2 - 1 painter.drawImage(x, y, self.__image) if self.__menu is not None: triagPath = QPainterPath() startPos = QPointF(self.width() - 5, self.height() - 3) triagPath.moveTo(startPos) triagPath.lineTo(startPos.x() + 4, startPos.y()) triagPath.lineTo(startPos.x() + 2, startPos.y() + 2) triagPath.closeSubpath() painter.setPen(Qt.black) painter.setBrush(Qt.black) painter.setRenderHint(QPainter.Antialiasing, False) painter.drawPath(triagPath)
def get_selected_edge(self, pos: QPointF, width_view: float): """ Bestimmt auf welcher Ecke der ROI der Mauszeiger gerade ist. 0 = links, 1 = rechts :param pos: In die Szene gemappte Position des Mauszeigers """ x1 = self.rect().x() x2 = x1 + self.rect().width() y1 = self.rect().y() y2 = y1 + self.rect().height() x = pos.x() y = pos.y() if x1 - 0.025 * width_view < x < x1 + 0.025 * width_view and y1 < y < y2: self.selected_edge = 0 return 0 if x2 - 0.025 * width_view < x < x2 + 0.025 * width_view and y1 < y < y2: self.selected_edge = 1 return 1 self.selected_edge = None return None
class GraphWidget(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.graphicsView = GraphicsView(self) self.graphicsView.setDragMode(QGraphicsView.RubberBandDrag) self.graphicsView.setOptimizationFlags(QGraphicsView.DontSavePainterState) self.graphicsView.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) self.graphicsView.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.graphicsView.setRenderHint(QPainter.Antialiasing, True) #Make a graphics scene self.scene = GraphicsScene() self.graphicsView.setScene(self.scene) #Create a graph that can contain nodes and edges self.graph = Graph(self, self.scene, self.graphicsView) self.tokensInScene = [] # initial zoomlevel self.zoomLevel = 250 #Final layout topLayout = QHBoxLayout() topLayout.setContentsMargins(0, 0, 0, 0) topLayout.setSpacing(0) topLayout.addWidget(self.graphicsView) self.setLayout(topLayout) def setGraph(self, graphData): if graphData != None: self.graphData = graphData # set widget size based on min/max positions of nodes if not self.graphData is None: minX, minY = sys.maxsize, sys.maxsize maxX, maxY = 0, 0 for n in self.graphData.nodes(): x, y = self.graphData.node[n]['pos'] minX = min(minX, x - 50) minY = min(minY, y - 50) maxX = max(maxX, x + 50) maxY = max(maxY, y + 50) # Determine the center of the graph self.centerOfGraph = QPointF((minX + maxX) / 2, (minY + maxY) / 2) self.placeGraphObjects() #Resize scene to be slightly larger than the graph self.scene.updateSceneRect() self.resetView() self.update() def placeGraphObjects(self): self.graph.clearGraph() self.scene.clear() self.tokensInScene.clear() #Place graph objects based on the graph data nodeList = [] nodePoints = [] for n in self.graphData.nodes(): #Place nodes x, y = self.graphData.node[n]['pos'] func = self.graphData.node[n]['funcstr'] clashCode = self.graphData.node[n]['clashcode'] r, g, b = self.graphData.node[n]['color'] self.graph.addNode(x, y, n, func, clashCode, QColor(r, g, b)) nPoint = [x, y] nodeList.append(n) nodePoints.append(nPoint) #Check for self-looping edges first for src, dst in self.graphData.edges(): #Place edges and tokens node1 = nodeList.index(src) node2 = nodeList.index(dst) if src == dst: tokenValues = self.graphData[src][dst]['tkns'] pRates = self.graphData[src][dst]['prates'] cRates = self.graphData[src][dst]['crates'] resnr = self.graphData[src][dst]['res'] argnr = self.graphData[src][dst]['arg'] r, g, b = self.graphData[src][dst]['color'] color = QColor(r, g, b) self.tokensInScene.append((src, dst)) self.graph.addEdgeToNodes(node1, node2, 'right', 'left', src, dst, tokenValues, pRates, cRates, resnr, argnr, color) #Then place the rest of the edges (not self-looping) for src, dst in self.graphData.edges(): #Place edges and tokens node1 = nodeList.index(src) node2 = nodeList.index(dst) if src != dst: tokenValues = self.graphData[src][dst]['tkns'] pRates = self.graphData[src][dst]['prates'] cRates = self.graphData[src][dst]['crates'] resnr = self.graphData[src][dst]['res'] argnr = self.graphData[src][dst]['arg'] r, g, b = self.graphData[src][dst]['color'] color = QColor(r, g, b) self.tokensInScene.append((src, dst)) #If begin node is left of end node if nodePoints[node1][0] < nodePoints[node2][0]: self.graph.addEdgeToNodes(node1, node2, 'right', 'left', src, dst, tokenValues, pRates, cRates, resnr, argnr, color) elif nodePoints[node1][0] > nodePoints[node2][0]: self.graph.addEdgeToNodes(node1, node2, 'left', 'right', src, dst, tokenValues, pRates, cRates, resnr, argnr, color) else: if nodePoints[node1][0] > self.centerOfGraph.x(): self.graph.addEdgeToNodes(node1, node2, 'right', 'right', src, dst, tokenValues, pRates, cRates, resnr, argnr, color) else: self.graph.addEdgeToNodes(node1, node2, 'left', 'left', src, dst, tokenValues, pRates, cRates, resnr, argnr, color) def updateTokensGraph(self): #Update tokens after a step i = 0 for src, dst in self.tokensInScene: self.graph.updateTokens(i, self.graphData[src][dst]['tkns']) i = i + 1 def editTokens(self, src, dst, newTokens): Log.addLogMessage(Log.INFO, 'Tokens for edge ' + str((src, dst)) + ' updated to ' + str(newTokens)) self.graphData[src][dst]['tkns'] = newTokens newTokens = str(newTokens) self.graphData.updateTokens((src, dst), newTokens) #Also store the state after the change (Makes it more visual and makes it possible to step back to) self.graphData._storestate() def editNodeFunction(self, nodeName, newFunction): Log.addLogMessage(Log.INFO, 'Function of node ' + nodeName + ' updated to ' + str(newFunction)) self.graphData.node[nodeName]['funcstr'] = newFunction self.graphData.updateNodeFunction(nodeName, newFunction) def editNodePosition(self, nodeName, newPos): # Log.addLogMessage(Log.INFO, 'Position of node ' + nodeName + ' updated to ' + str(newPos)) self.graphData.node[nodeName]['pos'] = newPos.x(), newPos.y() def editClashCode(self, nodeName, newClashCode): Log.addLogMessage(Log.INFO,'CLaSH code of ' + nodeName + ' updated') self.graphData.node[nodeName]['clashcode'] = newClashCode self.graphData.updateClashCode(nodeName, newClashCode) def editPRates(self, src, dst, newPRates): Log.addLogMessage(Log.INFO, 'Production rates for edge ' + str((src, dst)) + ' updated to ' + str(newPRates)) self.graphData[src][dst]['prates'] = newPRates newPRates = str(newPRates) self.graphData.updatePRates((src, dst), newPRates) def editCRates(self, src, dst, newCRates): Log.addLogMessage(Log.INFO, 'Consumption rates for edge ' + str((src, dst)) + ' updated to ' + str(newCRates)) self.graphData[src][dst]['crates'] = newCRates newCRates = str(newCRates) self.graphData.updateCRates((src, dst), newCRates) def getFireCount(self, src_dst, node): return self.graphData.node[src_dst]['firecount'] def resetView(self): self.zoomLevel = 250 self.setupMatrix() self.graphicsView.ensureVisible(QRectF(0,0,0,0)) def setupMatrix(self): scale = 2.0 ** ((self.zoomLevel - 250) / 50.0) transform = QTransform() transform.scale(scale, scale) self.graphicsView.setTransform(transform) def zoomIn(self): self.zoomLevel += 10 if self.zoomLevel > 350: self.zoomLevel = 350 self.setupMatrix() def zoomOut(self): self.zoomLevel -= 10 if self.zoomLevel < 0: self.zoomLevel = 0 self.setupMatrix()
class MapObject(Object): ## # Enumerates the different object shapes. Rectangle is the default shape. # When a polygon is set, the shape determines whether it should be # interpreted as a filled polygon or a line. ## Rectangle, Polygon, Polyline, Ellipse = range(4) def __init__(self, *args): super().__init__(Object.MapObjectType) self.mPolygon = QPolygonF() self.mName = QString() self.mPos = QPointF() self.mCell = Cell() self.mType = QString() self.mId = 0 self.mShape = MapObject.Rectangle self.mObjectGroup = None self.mRotation = 0.0 self.mVisible = True l = len(args) if l==0: self.mSize = QSizeF(0, 0) elif l==4: name, _type, pos, size = args self.mName = name self.mType = _type self.mPos = pos self.mSize = QSizeF(size) ## # Returns the id of this object. Each object gets an id assigned that is # unique for the map the object is on. ## def id(self): return self.mId ## # Sets the id of this object. ## def setId(self, id): self.mId = id ## # Returns the name of this object. The name is usually just used for # identification of the object in the editor. ## def name(self): return self.mName ## # Sets the name of this object. ## def setName(self, name): self.mName = name ## # Returns the type of this object. The type usually says something about # how the object is meant to be interpreted by the engine. ## def type(self): return self.mType ## # Sets the type of this object. ## def setType(self, type): self.mType = type ## # Returns the position of this object. ## def position(self): return QPointF(self.mPos) ## # Sets the position of this object. ## def setPosition(self, pos): self.mPos = pos ## # Returns the x position of this object. ## def x(self): return self.mPos.x() ## # Sets the x position of this object. ## def setX(self, x): self.mPos.setX(x) ## # Returns the y position of this object. ## def y(self): return self.mPos.y() ## # Sets the x position of this object. ## def setY(self, y): self.mPos.setY(y) ## # Returns the size of this object. ## def size(self): return self.mSize ## # Sets the size of this object. ## def setSize(self, *args): l = len(args) if l==1: size = args[0] self.mSize = QSizeF(size) elif l==2: width, height = args self.setSize(QSizeF(width, height)) ## # Returns the width of this object. ## def width(self): return self.mSize.width() ## # Sets the width of this object. ## def setWidth(self, width): self.mSize.setWidth(width) ## # Returns the height of this object. ## def height(self): return self.mSize.height() ## # Sets the height of this object. ## def setHeight(self, height): self.mSize.setHeight(height) ## # Sets the polygon associated with this object. The polygon is only used # when the object shape is set to either Polygon or Polyline. # # \sa setShape() ## def setPolygon(self, polygon): self.mPolygon = polygon ## # Returns the polygon associated with this object. Returns an empty # polygon when no polygon is associated with this object. ## def polygon(self): return QPolygonF(self.mPolygon) ## # Sets the shape of the object. ## def setShape(self, shape): self.mShape = shape ## # Returns the shape of the object. ## def shape(self): return self.mShape ## # Shortcut to getting a QRectF from position() and size(). ## def bounds(self): return QRectF(self.mPos, self.mSize) ## # Shortcut to getting a QRectF from position() and size() that uses cell tile if present. ## def boundsUseTile(self): if (self.mCell.isEmpty()): # No tile so just use regular bounds return self.bounds() # Using the tile for determing boundary # Note the position given is the bottom-left corner so correct for that return QRectF(QPointF(self.mPos.x(), self.mPos.y() - self.mCell.tile.height()), self.mCell.tile.size()) ## # Sets the tile that is associated with this object. The object will # display as the tile image. # # \warning The object shape is ignored for tile objects! ## def setCell(self, cell): self.mCell = cell ## # Returns the tile associated with this object. ## def cell(self): return self.mCell ## # Returns the object group this object belongs to. ## def objectGroup(self): return self.mObjectGroup ## # Sets the object group this object belongs to. Should only be called # from the ObjectGroup class. ## def setObjectGroup(self, objectGroup): self.mObjectGroup = objectGroup ## # Returns the rotation of the object in degrees. ## def rotation(self): return self.mRotation ## # Sets the rotation of the object in degrees. ## def setRotation(self, rotation): self.mRotation = rotation ## # This is somewhat of a workaround for dealing with the ways different objects # align. # # Traditional rectangle objects have top-left alignment. # Tile objects have bottom-left alignment on orthogonal maps, but # bottom-center alignment on isometric maps. # # Eventually, the object alignment should probably be configurable. For # backwards compatibility, it will need to be configurable on a per-object # level. ## def alignment(self): if (self.mCell.isEmpty()): return Alignment.TopLeft elif (self.mObjectGroup): map = self.mObjectGroup.map() if map: if (map.orientation() == Map.Orientation.Isometric): return Alignment.Bottom return Alignment.BottomLeft def isVisible(self): return self.mVisible def setVisible(self, visible): self.mVisible = visible ## # Flip this object in the given \a direction. This doesn't change the size # of the object. ## def flip(self, direction): if (not self.mCell.isEmpty()): if (direction == FlipDirection.FlipHorizontally): self.mCell.flippedHorizontally = not self.mCell.flippedHorizontally elif (direction == FlipDirection.FlipVertically): self.mCell.flippedVertically = not self.mCell.flippedVertically if (not self.mPolygon.isEmpty()): center2 = self.mPolygon.boundingRect().center() * 2 if (direction == FlipDirection.FlipHorizontally): for i in range(self.mPolygon.size()): # oh, QPointF mPolygon returned is a copy of internal object self.mPolygon[i] = QPointF(center2.x() - self.mPolygon[i].x(), self.mPolygon[i].y()) elif (direction == FlipDirection.FlipVertically): for i in range(self.mPolygon.size()): self.mPolygon[i] = QPointF(self.mPolygon[i].x(), center2.y() - self.mPolygon[i].y()) ## # Returns a duplicate of this object. The caller is responsible for the # ownership of this newly created object. ## def clone(self): o = MapObject(self.mName, self.mType, self.mPos, self.mSize) o.setProperties(self.properties()) o.setPolygon(self.mPolygon) o.setShape(self.mShape) o.setCell(self.mCell) o.setRotation(self.mRotation) return o