def create_winner_label(self): w = QGraphicsTextItem() w.setPlainText("%s" % self.mini_game_winner) game_board = self.parent() print(game_board) w.setFont(QFont('SansSerif', self.fonts[self.mini_game_winner])) w.setDefaultTextColor(Qt.black) w.setPos(self.x(), self.y()) w.setVisible(False) return w
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 QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.player = QMediaPlayer(self) #创建视频播放器 self.player.setNotifyInterval(1000) #信息更新周期, ms scene = QGraphicsScene(self) self.ui.graphicsView.setScene(scene) self.videoItem = QGraphicsVideoItem() #视频显示画面 self.videoItem.setSize(QSizeF(320, 220)) self.videoItem.setFlag(QGraphicsItem.ItemIsMovable) self.videoItem.setFlag(QGraphicsItem.ItemIsSelectable) self.videoItem.setFlag(QGraphicsItem.ItemIsFocusable) scene.addItem(self.videoItem) self.player.setVideoOutput(self.videoItem) #设置视频显示图形项 self.textItem = QGraphicsTextItem("面朝大海,春暖花开") #弹幕文字 font = self.textItem.font() font.setPointSize(20) self.textItem.setFont(font) self.textItem.setDefaultTextColor(Qt.red) self.textItem.setPos(100, 220) self.textItem.setFlag(QGraphicsItem.ItemIsMovable) self.textItem.setFlag(QGraphicsItem.ItemIsSelectable) self.textItem.setFlag(QGraphicsItem.ItemIsFocusable) scene.addItem(self.textItem) self.ui.btnText.setCheckable(True) #弹幕文字按钮 self.ui.btnText.setChecked(True) self.__duration = "" self.__curPos = "" self.player.stateChanged.connect(self.do_stateChanged) self.player.positionChanged.connect(self.do_positionChanged) self.player.durationChanged.connect(self.do_durationChanged) ## ==============自定义功能函数======================== ## ==============event处理函数========================== def closeEvent(self, event): #窗体关闭时 # 窗口关闭时不能自动停止播放,需手动停止 if (self.player.state() == QMediaPlayer.PlayingState): self.player.stop() ## ==========由connectSlotsByName()自动连接的槽函数============ @pyqtSlot() ##打开文件 def on_btnOpen_clicked(self): curPath = QDir.currentPath() #获取系统当前目录 ## curPath=os.getcwd() title = "选择视频文件" filt = "视频文件(*.wmv *.avi);;所有文件(*.*)" fileName, flt = QFileDialog.getOpenFileName(self, title, curPath, filt) if (fileName == ""): return fileInfo = QFileInfo(fileName) baseName = fileInfo.fileName() ## baseName=os.path.basename(fileName) self.ui.LabCurMedia.setText(baseName) curPath = fileInfo.absolutePath() QDir.setCurrent(curPath) #重设当前目录 media = QMediaContent(QUrl.fromLocalFile(fileName)) self.player.setMedia(media) #设置播放文件 self.player.play() @pyqtSlot() ##播放 def on_btnPlay_clicked(self): self.player.play() @pyqtSlot() ##暂停 def on_btnPause_clicked(self): self.player.pause() @pyqtSlot() ##停止 def on_btnStop_clicked(self): self.player.stop() @pyqtSlot() ##全屏 def on_btnFullScreen_clicked(self): self.videoWidget.setFullScreen(True) @pyqtSlot() ##静音按钮 def on_btnSound_clicked(self): mute = self.player.isMuted() self.player.setMuted(not mute) if mute: self.ui.btnSound.setIcon(QIcon(":/icons/images/volumn.bmp")) else: self.ui.btnSound.setIcon(QIcon(":/icons/images/mute.bmp")) @pyqtSlot(int) ##音量调节 def on_sliderVolumn_valueChanged(self, value): self.player.setVolume(value) @pyqtSlot(int) ##播放进度调节 def on_sliderPosition_valueChanged(self, value): self.player.setPosition(value) @pyqtSlot() ##放大 def on_btnZoomIn_clicked(self): sc = self.videoItem.scale() self.videoItem.setScale(sc + 0.1) @pyqtSlot() ##缩小 def on_btnZoomOut_clicked(self): sc = self.videoItem.scale() self.videoItem.setScale(sc - 0.1) @pyqtSlot(bool) ##弹幕 def on_btnText_clicked(self, checked): self.textItem.setVisible(checked) ## =============自定义槽函数=============================== def do_stateChanged(self, state): isPlaying = (state == QMediaPlayer.PlayingState) self.ui.btnPlay.setEnabled(not isPlaying) self.ui.btnPause.setEnabled(isPlaying) self.ui.btnStop.setEnabled(isPlaying) def do_durationChanged(self, duration): self.ui.sliderPosition.setMaximum(duration) secs = duration / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__duration = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration) def do_positionChanged(self, position): if (self.ui.sliderPosition.isSliderDown()): return #如果正在拖动滑条,退出 self.ui.sliderPosition.setSliderPosition(position) secs = position / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__curPos = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration)
class CollectDataWidget(QGraphicsView): yIncr = 10 xIncr = 20 outOfBounds = False margin = 25 trainingMode = 1 timerLength = 20 def __init__(self): super(CollectDataWidget, self).__init__() screen = QDesktopWidget().screenGeometry() self.setGeometry(screen) self.setWindowTitle("Data Collection") self.scene = QGraphicsScene(self) self.scene.setSceneRect(0, 0, screen.right() - self.margin, screen.bottom() - self.margin) self.node = Node(self) self.scene.addItem(self.node) # aktualnie 3:24 z (dx,dy, lambda) = (10, 20, 20) text = "Keep your eyes on the blue circle - testing once begun takes ~2 min. - press <Space> to begin" self.text = QGraphicsTextItem(text) self.text.setPos(100, 0) self.scene.addItem(self.text) self.thread = CameraThread(self.node) self.setScene(self.scene) self.showFullScreen() def timerEvent(self, event): dim = self.scene.sceneRect().bottomRight() node = self.node pos = node.scenePos() if self.trainingMode == 0: mousePos = getMousePos() elif self.trainingMode == 1: if pos.x() > dim.x(): self.close() elif (pos.y() > dim.y() or pos.y() <= 0) and not self.outOfBounds: node.moveBy(abs(self.xIncr), 0) self.yIncr *= -1 self.outOfBounds = True else: node.moveBy(0, self.yIncr) if self.outOfBounds: self.outOfBounds = False elif self.trainingMode == 2: print("Hello") radius = self.node.r x = random.randint(radius, dim.x() - radius) y = random.randint(radius, dim.y() - radius) print(x, y) node.setPos(x, y) def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Q: self.thread.terminate = True self.close() elif key == Qt.Key_0: self.trainingMode = 0 self.timerLength = 50 elif key == Qt.Key_1: print("Training mode 1 selected") self.trainingMode = 1 self.timerLength = 20 elif key == Qt.Key_2: print("Training mode 2 selected") self.trainingMode = 2 self.timerLength = 2000 elif key == Qt.Key_V: self.node.setVisible(not self.node.isVisible()) elif key == Qt.Key_Space: self.startTimer(self.timerLength) self.thread.start() self.text.setVisible(False)
class BlockItem(QGraphicsPixmapItem): def __init__(self, trnsysType, parent, **kwargs): super().__init__(None) self.logger = parent.logger self.w = 120 self.h = 120 self.parent = parent self.id = self.parent.parent().idGen.getID() self.propertyFile = [] if "displayName" in kwargs: self.displayName = kwargs["displayName"] else: self.displayName = trnsysType + "_" + str(self.id) if "loadedBlock" not in kwargs: self.parent.parent().trnsysObj.append(self) self.inputs = [] self.outputs = [] # Export related: self.name = trnsysType self.trnsysId = self.parent.parent().idGen.getTrnsysID() # Transform related self.flippedV = False self.flippedH = False self.rotationN = 0 self.flippedHInt = -1 self.flippedVInt = -1 pixmap = self._getPixmap() self.setPixmap(pixmap) # To set flags of this item self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) self.setFlag(self.ItemSendsScenePositionChanges, True) self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) self.label = QGraphicsTextItem(self.displayName, self) self.label.setVisible(False) if self.name == "Bvi": self.inputs.append(_cspi.createSinglePipePortItem("i", 0, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) if self.name == "StorageTank": # Inputs get appended in ConfigStorage pass self.logger.debug("Block name is " + str(self.name)) # Update size for generic block: if self.name == "Bvi": self.changeSize() # Experimental, used for detecting genereated blocks attached to storage ports self.inFirstRow = False # Undo framework related self.oldPos = None self.origOutputsPos = None self.origInputsPos = None def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: if type(self) == BlockItem: raise AssertionError( "`BlockItem' cannot be instantiated directly.") currentClassName = BlockItem.__name__ currentMethodName = f"{currentClassName}.{BlockItem._getImageAccessor.__name__}" message = ( f"{currentMethodName} has been called. However, this method should not be called directly but must\n" f"implemented in a child class. This means that a) someone instantiated `{currentClassName}` directly\n" f"or b) a child class of it doesn't implement `{currentMethodName}`. Either way that's an\n" f"unrecoverable error and therefore the program will be terminated now. Please do get in touch with\n" f"the developers if you've encountered this error. Thanks.") exception = AssertionError(message) # I've seen exception messages mysteriously swallowed that's why we're logging the message here, too. self.logger.error(message, exc_info=exception, stack_info=True) raise exception def addTree(self): pass # Setter functions def setParent(self, p): self.parent = p if self not in self.parent.parent().trnsysObj: self.parent.parent().trnsysObj.append(self) # self.logger.debug("trnsysObj are " + str(self.parent.parent().trnsysObj)) def setId(self, newId): self.id = newId def setName(self, newName): self.displayName = newName self.label.setPlainText(newName) # Interaction related def contextMenuEvent(self, event): menu = QMenu() a1 = menu.addAction("Launch NotePad++") a1.triggered.connect(self.launchNotepadFile) rr = _img.ROTATE_TO_RIGHT_PNG.icon() a2 = menu.addAction(rr, "Rotate Block clockwise") a2.triggered.connect(self.rotateBlockCW) ll = _img.ROTATE_LEFT_PNG.icon() a3 = menu.addAction(ll, "Rotate Block counter-clockwise") a3.triggered.connect(self.rotateBlockCCW) a4 = menu.addAction("Reset Rotation") a4.triggered.connect(self.resetRotation) b1 = menu.addAction("Print Rotation") b1.triggered.connect(self.printRotation) c1 = menu.addAction("Delete this Block") c1.triggered.connect(self.deleteBlockCom) menu.exec_(event.screenPos()) def launchNotepadFile(self): self.logger.debug("Launching notpad") global FilePath os.system("start notepad++ " + FilePath) def mouseDoubleClickEvent(self, event): if hasattr(self, "isTempering"): self.parent.parent().showTVentilDlg(self) elif self.name == "Pump": self.parent.parent().showPumpDlg(self) elif self.name == "TeePiece" or self.name == "WTap_main": self.parent.parent().showBlockDlg(self) elif self.name in ["SPCnr", "DPCnr", "DPTee"]: self.parent.parent().showDoublePipeBlockDlg(self) else: self.parent.parent().showBlockDlg(self) if len(self.propertyFile) > 0: for files in self.propertyFile: os.startfile(files, "open") def mouseReleaseEvent(self, event): # self.logger.debug("Released mouse over block") if self.oldPos is None: self.logger.debug("For Undo Framework: oldPos is None") else: if self.scenePos() != self.oldPos: self.logger.debug("Block was dragged") self.logger.debug("Old pos is" + str(self.oldPos)) command = MoveCommand(self, self.oldPos, "Move BlockItem") self.parent.parent().parent().undoStack.push(command) self.oldPos = self.scenePos() super(BlockItem, self).mouseReleaseEvent(event) # Transform related def changeSize(self): self._positionLabel() w, h = self._getCappedWithAndHeight() if self.name == "Bvi": delta = 4 self.inputs[0].setPos( -2 * delta + 4 * self.flippedH * delta + self.flippedH * w, h / 3) self.outputs[0].setPos( -2 * delta + 4 * self.flippedH * delta + self.flippedH * w, 2 * h / 3) self.inputs[0].side = 0 + 2 * self.flippedH self.outputs[0].side = 0 + 2 * self.flippedH def _positionLabel(self): width, height = self._getCappedWithAndHeight() rect = self.label.boundingRect() labelWidth, lableHeight = rect.width(), rect.height() labelPosX = (height - labelWidth) / 2 self.label.setPos(labelPosX, width) def _getCappedWithAndHeight(self): width = self.w height = self.h if height < 20: height = 20 if width < 40: width = 40 return width, height def updateFlipStateH(self, state): self.flippedH = bool(state) pixmap = self._getPixmap() self.setPixmap(pixmap) self.flippedHInt = 1 if self.flippedH else -1 if self.flippedH: for i in range(0, len(self.inputs)): distanceToMirrorAxis = self.w / 2.0 - self.origInputsPos[i][0] self.inputs[i].setPos( self.origInputsPos[i][0] + 2.0 * distanceToMirrorAxis, self.inputs[i].pos().y(), ) for i in range(0, len(self.outputs)): distanceToMirrorAxis = self.w / 2.0 - self.origOutputsPos[i][0] self.outputs[i].setPos( self.origOutputsPos[i][0] + 2.0 * distanceToMirrorAxis, self.outputs[i].pos().y(), ) else: for i in range(0, len(self.inputs)): self.inputs[i].setPos(self.origInputsPos[i][0], self.inputs[i].pos().y()) for i in range(0, len(self.outputs)): self.outputs[i].setPos(self.origOutputsPos[i][0], self.outputs[i].pos().y()) def updateFlipStateV(self, state): self.flippedV = bool(state) pixmap = self._getPixmap() self.setPixmap(pixmap) self.flippedVInt = 1 if self.flippedV else -1 if self.flippedV: for i in range(0, len(self.inputs)): distanceToMirrorAxis = self.h / 2.0 - self.origInputsPos[i][1] self.inputs[i].setPos( self.inputs[i].pos().x(), self.origInputsPos[i][1] + 2.0 * distanceToMirrorAxis, ) for i in range(0, len(self.outputs)): distanceToMirrorAxis = self.h / 2.0 - self.origOutputsPos[i][1] self.outputs[i].setPos( self.outputs[i].pos().x(), self.origOutputsPos[i][1] + 2.0 * distanceToMirrorAxis, ) else: for i in range(0, len(self.inputs)): self.inputs[i].setPos(self.inputs[i].pos().x(), self.origInputsPos[i][1]) for i in range(0, len(self.outputs)): self.outputs[i].setPos(self.outputs[i].pos().x(), self.origOutputsPos[i][1]) def updateSidesFlippedH(self): if self.rotationN % 2 == 0: for p in self.inputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) for p in self.outputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) if self.rotationN % 2 == 1: for p in self.inputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) for p in self.outputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) def updateSidesFlippedV(self): if self.rotationN % 2 == 1: for p in self.inputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) for p in self.outputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) if self.rotationN % 2 == 0: for p in self.inputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) for p in self.outputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) def updateSide(self, port, n): port.side = (port.side + n) % 4 # self.logger.debug("Port side is " + str(port.side)) def rotateBlockCW(self): # Rotate block clockwise # self.setTransformOriginPoint(50, 50) # self.setTransformOriginPoint(self.w/2, self.h/2) self.setTransformOriginPoint(0, 0) self.setRotation((self.rotationN + 1) * 90) self.label.setRotation(-(self.rotationN + 1) * 90) self.rotationN += 1 self.logger.debug("rotated by " + str(self.rotationN)) for p in self.inputs: p.itemChange(27, p.scenePos()) self.updateSide(p, 1) for p in self.outputs: p.itemChange(27, p.scenePos()) self.updateSide(p, 1) pixmap = self._getPixmap() self.setPixmap(pixmap) def rotateBlockToN(self, n): if n > 0: while self.rotationN != n: self.rotateBlockCW() if n < 0: while self.rotationN != n: self.rotateBlockCCW() def rotateBlockCCW(self): # Rotate block clockwise # self.setTransformOriginPoint(50, 50) self.setTransformOriginPoint(0, 0) self.setRotation((self.rotationN - 1) * 90) self.label.setRotation(-(self.rotationN - 1) * 90) self.rotationN -= 1 self.logger.debug("rotated by " + str(self.rotationN)) for p in self.inputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -1) for p in self.outputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -1) pixmap = self._getPixmap() self.setPixmap(pixmap) def resetRotation(self): self.logger.debug("Resetting rotation...") self.setRotation(0) self.label.setRotation(0) for p in self.inputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -self.rotationN) # self.logger.debug("Portside of port " + str(p) + " is " + str(p.portSide)) for p in self.outputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -self.rotationN) # self.logger.debug("Portside of port " + str(p) + " is " + str(p.portSide)) self.rotationN = 0 pixmap = self._getPixmap() self.setPixmap(pixmap) def printRotation(self): self.logger.debug("Rotation is " + str(self.rotationN)) # Deletion related def deleteConns(self): for p in self.inputs: while len(p.connectionList) > 0: p.connectionList[0].deleteConn() for p in self.outputs: while len(p.connectionList) > 0: p.connectionList[0].deleteConn() def deleteBlock(self): self.parent.parent().trnsysObj.remove(self) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild( QTreeView, self.displayName + "Tree") if widgetToRemove: widgetToRemove.hide() def deleteBlockCom(self): self.parent.deleteBlockCom(self) def getConnections(self): """ Get the connections from inputs and outputs of this block. Returns ------- c : :obj:`List` of :obj:`BlockItem` """ c = [] for i in self.inputs: for cl in i.connectionList: c.append(cl) for o in self.outputs: for cl in o.connectionList: c.append(cl) return c # Scaling related def mousePressEvent(self, event): # create resizer """ Using try catch to avoid creating extra resizers. When an item is clicked on, it will check if a resizer already existed. If there exist a resizer, returns. else, creates one. Resizer will not be created for GenericBlock due to complications in the code. Resizer will not be created for storageTank as there's already a built in function for it in the storageTank dialog. Resizers are deleted inside mousePressEvent function inside GUI.py """ self.logger.debug("Inside Block Item mouse click") self.isSelected = True if self.name == "GenericBlock" or self.name == "StorageTank": return try: self.resizer except AttributeError: self.resizer = ResizerItem(self) self.resizer.setPos(self.w, self.h) self.resizer.itemChange(self.resizer.ItemPositionChange, self.resizer.pos()) else: return def setItemSize(self, w, h): self.logger.debug("Inside block item set item size") self.w, self.h = w, h # if h < 20: # self.h = 20 # if w < 40: # self.w = 40 def updateImage(self): self.logger.debug("Inside block item update image") pixmap = self._getPixmap() self.setPixmap(pixmap) if self.flippedH: self.updateFlipStateH(self.flippedH) if self.flippedV: self.updateFlipStateV(self.flippedV) def _getPixmap(self) -> QPixmap: imageAccessor = self._getImageAccessor() image = imageAccessor.image(width=self.w, height=self.h).mirrored( horizontal=self.flippedH, vertical=self.flippedV) pixmap = QPixmap(image) return pixmap def deleteResizer(self): try: self.resizer except AttributeError: self.logger.debug("No resizer") else: del self.resizer # AlignMode related 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 alignBlock(self, value): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem) and t is not self: if self.elementInYBand(t): value = QPointF(self.pos().x(), t.pos().y()) self.parent.parent().alignYLineItem.setLine( self.pos().x() + self.w / 2, t.pos().y(), t.pos().x() + t.w / 2, t.pos().y()) self.parent.parent().alignYLineItem.setVisible(True) qtm = QTimer(self.parent.parent()) qtm.timeout.connect(self.timerfunc) qtm.setSingleShot(True) qtm.start(1000) e = QMouseEvent( QEvent.MouseButtonRelease, self.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier, ) self.parent.mouseReleaseEvent(e) self.parent.parent().alignMode = False # self.setPos(self.pos().x(), t.pos().y()) # self.aligned = True if self.elementInXBand(t): value = QPointF(t.pos().x(), self.pos().y()) self.parent.parent().alignXLineItem.setLine( t.pos().x(), t.pos().y() + self.w / 2, t.pos().x(), self.pos().y() + t.w / 2) self.parent.parent().alignXLineItem.setVisible(True) qtm = QTimer(self.parent.parent()) qtm.timeout.connect(self.timerfunc2) qtm.setSingleShot(True) qtm.start(1000) e = QMouseEvent( QEvent.MouseButtonRelease, self.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier, ) self.parent.mouseReleaseEvent(e) self.parent.parent().alignMode = False return value def timerfunc(self): self.parent.parent().alignYLineItem.setVisible(False) def timerfunc2(self): self.parent.parent().alignXLineItem.setVisible(False) def hasElementsInYBand(self): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem): if self.elementInYBand(t): return True return False def hasElementsInXBand(self): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem): if self.elementInXBand(t): return True return False def elementInYBand(self, t): eps = 50 return self.scenePos().y() - eps <= t.scenePos().y( ) <= self.scenePos().y() + eps def elementInXBand(self, t): eps = 50 return self.scenePos().x() - eps <= t.scenePos().x( ) <= self.scenePos().x() + eps def elementInY(self): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem): if self.scenePos().y == t.scenePos().y(): return True return False def encode(self): portListInputs = [] portListOutputs = [] for inp in self.inputs: portListInputs.append(inp.id) for output in self.outputs: portListOutputs.append(output.id) blockPosition = (float(self.pos().x()), float(self.pos().y())) blockItemModel = BlockItemModel( self.name, self.displayName, blockPosition, self.id, self.trnsysId, portListInputs, portListOutputs, self.flippedH, self.flippedV, self.rotationN, ) dictName = "Block-" return dictName, blockItemModel.to_dict() def decode(self, i, resBlockList): model = BlockItemModel.from_dict(i) self.setName(model.BlockDisplayName) self.setPos(float(model.blockPosition[0]), float(model.blockPosition[1])) self.id = model.Id self.trnsysId = model.trnsysId if len(self.inputs) != len(model.portsIdsIn) or len( self.outputs) != len(model.portsIdsOut): temp = model.portsIdsIn model.portsIdsIn = model.portsIdsOut model.portsIdsOut = temp for index, inp in enumerate(self.inputs): inp.id = model.portsIdsIn[index] for index, out in enumerate(self.outputs): out.id = model.portsIdsOut[index] self.updateFlipStateH(model.flippedH) self.updateFlipStateV(model.flippedV) self.rotateBlockToN(model.rotationN) resBlockList.append(self) def decodePaste(self, i, offset_x, offset_y, resConnList, resBlockList, **kwargs): self.setPos( float(i["BlockPosition"][0] + offset_x), float(i["BlockPosition"][1] + offset_y), ) self.updateFlipStateH(i["FlippedH"]) self.updateFlipStateV(i["FlippedV"]) self.rotateBlockToN(i["RotationN"]) for x in range(len(self.inputs)): self.inputs[x].id = i["PortsIDIn"][x] for x in range(len(self.outputs)): self.outputs[x].id = i["PortsIDOut"][x] resBlockList.append(self) # Export related def exportBlackBox(self): equation = [] if (len(self.inputs + self.outputs) == 2 and self.isVisible() and not isinstance(self.outputs[0], DoublePipePortItem)): files = glob.glob(os.path.join(self.path, "**/*.ddck"), recursive=True) if not (files): status = "noDdckFile" else: status = "noDdckEntry" lines = [] for file in files: infile = open(file, "r") lines += infile.readlines() for i in range(len(lines)): if "output" in lines[i].lower() and "to" in lines[i].lower( ) and "hydraulic" in lines[i].lower(): for j in range(i, len(lines) - i): if lines[j][0] == "T": outputT = lines[j].split("=")[0].replace(" ", "") status = "success" break equation = ["T" + self.displayName + "=" + outputT] break else: status = "noBlackBoxOutput" if status == "noDdckFile" or status == "noDdckEntry": equation.append("T" + self.displayName + "=1") return status, equation def exportPumpOutlets(self): return "", 0 def exportMassFlows(self): return "", 0 def exportDivSetting1(self): return "", 0 def exportDivSetting2(self, nUnit): return "", nUnit def exportPipeAndTeeTypesForTemp(self, startingUnit): return "", startingUnit def getTemperatureVariableName(self, portItem: SinglePipePortItem) -> str: return f"T{self.displayName}" def getFlowSolverParametersId(self, portItem: SinglePipePortItem) -> int: return self.trnsysId def assignIDsToUninitializedValuesAfterJsonFormatMigration( self, generator: _id.IdGenerator) -> None: pass def deleteLoadedFile(self): for items in self.loadedFiles: try: self.parent.parent().fileList.remove(str(items)) except ValueError: self.logger.debug("File already deleted from file list.") self.logger.debug("filelist:", self.parent.parent().fileList)
class LinkItem(QGraphicsObject): """ A Link item in the canvas that connects two :class:`.NodeItem`\s in the canvas. The link curve connects two `Anchor` items (see :func:`setSourceItem` and :func:`setSinkItem`). Once the anchors are set the curve automatically adjusts its end points whenever the anchors move. An optional source/sink text item can be displayed above the curve's central point (:func:`setSourceName`, :func:`setSinkName`) """ #: Z value of the item Z_VALUE = 0 def __init__(self, *args): self.__boundingRect = None QGraphicsObject.__init__(self, *args) self.setFlag(QGraphicsItem.ItemHasNoContents, True) self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton) self.setAcceptHoverEvents(True) self.setZValue(self.Z_VALUE) self.sourceItem = None self.sourceAnchor = None self.sinkItem = None self.sinkAnchor = None self.curveItem = LinkCurveItem(self) self.sourceIndicator = LinkAnchorIndicator(self) self.sinkIndicator = LinkAnchorIndicator(self) self.sourceIndicator.hide() self.sinkIndicator.hide() self.linkTextItem = QGraphicsTextItem(self) self.__sourceName = "" self.__sinkName = "" self.__dynamic = False self.__dynamicEnabled = False self.hover = False self.prepareGeometryChange() self.__boundingRect = None def setSourceItem(self, item, anchor=None): """ Set the source `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new output anchor will be created using ``item.newOutputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one end of the link). """ if item is not None and anchor is not None: if anchor not in item.outputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sourceItem != item: if self.sourceAnchor: # Remove a previous source item and the corresponding anchor self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) if self.sourceItem is not None: self.sourceItem.removeOutputAnchor(self.sourceAnchor) self.sourceItem = self.sourceAnchor = None self.sourceItem = item if item is not None and anchor is None: # Create a new output anchor for the item if none is provided. anchor = item.newOutputAnchor() # Update the visibility of the start point indicator. self.sourceIndicator.setVisible(bool(item)) if anchor != self.sourceAnchor: if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) self.sourceAnchor = anchor if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.connect( self._sourcePosChanged) self.__updateCurve() def setSinkItem(self, item, anchor=None): """ Set the sink `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new input anchor will be created using ``item.newInputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one and of the link). """ if item is not None and anchor is not None: if anchor not in item.inputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sinkItem != item: if self.sinkAnchor: # Remove a previous source item and the corresponding anchor self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) if self.sinkItem is not None: self.sinkItem.removeInputAnchor(self.sinkAnchor) self.sinkItem = self.sinkAnchor = None self.sinkItem = item if item is not None and anchor is None: # Create a new input anchor for the item if none is provided. anchor = item.newInputAnchor() # Update the visibility of the end point indicator. self.sinkIndicator.setVisible(bool(item)) if self.sinkAnchor != anchor: if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) self.sinkAnchor = anchor if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.connect( self._sinkPosChanged) self.__updateCurve() def setFont(self, font): """ Set the font for the channel names text item. """ if font != self.font(): self.linkTextItem.setFont(font) self.__updateText() def font(self): """ Return the font for the channel names text. """ return self.linkTextItem.font() def setChannelNamesVisible(self, visible): """ Set the visibility of the channel name text. """ self.linkTextItem.setVisible(visible) def setSourceName(self, name): """ Set the name of the source (used in channel name text). """ if self.__sourceName != name: self.__sourceName = name self.__updateText() def sourceName(self): """ Return the source name. """ return self.__sourceName def setSinkName(self, name): """ Set the name of the sink (used in channel name text). """ if self.__sinkName != name: self.__sinkName = name self.__updateText() def sinkName(self): """ Return the sink name. """ return self.__sinkName def _sinkPosChanged(self, *arg): self.__updateCurve() def _sourcePosChanged(self, *arg): self.__updateCurve() def __updateCurve(self): self.prepareGeometryChange() self.__boundingRect = None if self.sourceAnchor and self.sinkAnchor: source_pos = self.sourceAnchor.anchorScenePos() sink_pos = self.sinkAnchor.anchorScenePos() source_pos = self.curveItem.mapFromScene(source_pos) sink_pos = self.curveItem.mapFromScene(sink_pos) # Adaptive offset for the curve control points to avoid a # cusp when the two points have the same y coordinate # and are close together delta = source_pos - sink_pos dist = math.sqrt(delta.x()**2 + delta.y()**2) cp_offset = min(dist / 2.0, 60.0) # TODO: make the curve tangent orthogonal to the anchors path. path = QPainterPath() path.moveTo(source_pos) path.cubicTo(source_pos + QPointF(cp_offset, 0), sink_pos - QPointF(cp_offset, 0), sink_pos) self.curveItem.setPath(path) self.sourceIndicator.setPos(source_pos) self.sinkIndicator.setPos(sink_pos) self.__updateText() else: self.setHoverState(False) self.curveItem.setPath(QPainterPath()) def __updateText(self): self.prepareGeometryChange() self.__boundingRect = None if self.__sourceName or self.__sinkName: if self.__sourceName != self.__sinkName: text = u"{0} \u2192 {1}".format(self.__sourceName, self.__sinkName) else: # If the names are the same show only one. # Is this right? If the sink has two input channels of the # same type having the name on the link help elucidate # the scheme. text = self.__sourceName else: text = "" self.linkTextItem.setPlainText(text) path = self.curveItem.path() if not path.isEmpty(): center = path.pointAtPercent(0.5) angle = path.angleAtPercent(0.5) brect = self.linkTextItem.boundingRect() transform = QTransform() transform.translate(center.x(), center.y()) transform.rotate(-angle) # Center and move above the curve path. transform.translate(-brect.width() / 2, -brect.height()) self.linkTextItem.setTransform(transform) def removeLink(self): self.setSinkItem(None) self.setSourceItem(None) self.__updateCurve() def setHoverState(self, state): if self.hover != state: self.prepareGeometryChange() self.__boundingRect = None self.hover = state self.sinkIndicator.setHoverState(state) self.sourceIndicator.setHoverState(state) self.curveItem.setHoverState(state) def hoverEnterEvent(self, event): # Hover enter event happens when the mouse enters any child object # but we only want to show the 'hovered' shadow when the mouse # is over the 'curveItem', so we install self as an event filter # on the LinkCurveItem and listen to its hover events. self.curveItem.installSceneEventFilter(self) return QGraphicsObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): # Remove the event filter to prevent unnecessary work in # scene event filter when not needed self.curveItem.removeSceneEventFilter(self) return QGraphicsObject.hoverLeaveEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.curveItem: if event.type() == QEvent.GraphicsSceneHoverEnter: self.setHoverState(True) elif event.type() == QEvent.GraphicsSceneHoverLeave: self.setHoverState(False) return QGraphicsObject.sceneEventFilter(self, obj, event) def boundingRect(self): if self.__boundingRect is None: self.__boundingRect = self.childrenBoundingRect() return self.__boundingRect def shape(self): return self.curveItem.shape() def setEnabled(self, enabled): """ Reimplemented from :class:`QGraphicsObject` Set link enabled state. When disabled the link is rendered with a dashed line. """ # This getter/setter pair override a property from the base class. # They should be renamed to e.g. setLinkEnabled/linkEnabled self.curveItem.setLinkEnabled(enabled) def isEnabled(self): return self.curveItem.isLinkEnabled() def setDynamicEnabled(self, enabled): """ Set the link's dynamic enabled state. If the link is `dynamic` it will be rendered in red/green color respectively depending on the state of the dynamic enabled state. """ if self.__dynamicEnabled != enabled: self.__dynamicEnabled = enabled if self.__dynamic: self.__updatePen() def isDynamicEnabled(self): """ Is the link dynamic enabled. """ return self.__dynamicEnabled def setDynamic(self, dynamic): """ Mark the link as dynamic (i.e. it responds to :func:`setDynamicEnabled`). """ if self.__dynamic != dynamic: self.__dynamic = dynamic self.__updatePen() def isDynamic(self): """ Is the link dynamic. """ return self.__dynamic def __updatePen(self): self.prepareGeometryChange() self.__boundingRect = None if self.__dynamic: if self.__dynamicEnabled: color = QColor(0, 150, 0, 150) else: color = QColor(150, 0, 0, 150) normal = QPen(QBrush(color), 2.0) hover = QPen(QBrush(color.darker(120)), 2.1) else: normal = QPen(QBrush(QColor("#9CACB4")), 2.0) hover = QPen(QBrush(QColor("#7D7D7D")), 2.1) self.curveItem.setCurvePenSet(normal, hover)
class SegmentItemBase(QGraphicsItemGroup): def __init__(self, startNode, endNode, parent: "ConnectionBase"): """ A connection is displayed as a chain of segmentItems (stored in Connection.segments) Parameters. ---------- startNode endNode parent: type(parent): Connection """ super().__init__(None) self.logger = parent.logger self.setFlag(self.ItemIsSelectable, True) self.dragged = False self.initialised = False self.connection = parent self.firstChild = None self.secondChild = None self.cornerChild = None self.linePoints = None self.startNode = startNode self.endNode = endNode # These nodes are the nodes before and after the crossing self.start = None self.end = None # Unused. Related to interrupting segments for a clearer diagram self.disrBeforeNode = None self.disrAfterNode = None self.disrBeforeSeg = None self.disrAfterSeg = None self.disrBefore = False self.disrAfter = False self.hasBridge = False self.bridgedSegment = None # Only for editorMode 1 self.firstLine = None self.secondLine = None self.secondCorner = None self.thirdCorner = None self.keyPr = 0 # Used to only create the child objects once self._isDraggingInProgress = False self.insertInParentSegments() self.label = QGraphicsTextItem(self.connection.displayName) self.connection.parent.diagramScene.addItem(self.label) self.label.setVisible(False) self.label.setFlag(self.ItemIsMovable, True) self.labelMass = QGraphicsTextItem() self.connection.parent.diagramScene.addItem(self.labelMass) self.labelMass.setVisible(False) self.labelMass.setFlag(self.ItemIsMovable, True) self.setToolTip(self.connection.displayName) def segLength(self): return calcDist(self.line().p1(), self.line().p2()) def interpolate( self, partLen2, totLenConn, ): # c1_r = 0 # c1_b = 255 c1_r = 160 c1_b = 160 c1_g = 160 # c2_r = 255 # c2_b = 0 c2_r = 0 c2_b = 0 c2_g = 0 try: f1 = int(partLen2 / totLenConn) f2 = int((totLenConn - partLen2) / totLenConn) except ZeroDivisionError: return QColor(100, 100, 100) else: return QColor(f1 * c2_r + f2 * c1_r, f1 * c2_g + f2 * c1_g, f1 * c2_b + f2 * c1_b) def line(self): return self.linePoints def setLine(self, *args): self.setZValue(-1) if len(args) == 2: p1, p2 = args x1 = p1.x() y1 = p1.y() x2 = p2.x() y2 = p2.y() else: x1, y1, x2, y2 = args self._setLineImpl(x1, y1, x2, y2) def _setLineImpl(self, x1, y1, x2, y2): raise NotImplementedError() def updateGrad(self): raise NotImplementedError() def insertInParentSegments(self): """ This function inserts the segment in correct order to the segment list of the connection. Returns ------- """ prevSeg = None for s in self.connection.segments: if s.endNode is self.startNode: prevSeg = s # Todo: Add support for disr segments # if the startNode parent is a connection: if not hasattr(self.startNode.parent, "fromPort"): self.connection.segments.insert( self.connection.segments.index(prevSeg) + 1, self) else: self.connection.segments.insert(0, self) def mousePressEvent(self, e): if e.button() == 1: self.keyPr = 1 self.logger.debug("Setting key to 1") self.connection.selectConnection() if self.isVertical(): try: self.oldX = self.startNode.parent.scenePos().x() except AttributeError: pass else: self.logger.debug("set oldx") def mouseMoveEvent(self, e): self.logger.debug(self.connection.parent.editorMode) if self.keyPr == 1: self.logger.debug("moved with button 1") newPos = e.pos() if self.connection.parent.editorMode == 0: if not self._isDraggingInProgress: self.initInMode0() else: self.dragInMode0(newPos) elif self.connection.parent.editorMode == 1: if type(self.startNode.parent) is CornerItem and type( self.endNode.parent) is CornerItem: if not self.startNode.parent.isVisible(): self.startNode.parent.setVisible(True) if not self.endNode.parent.isVisible(): self.endNode.parent.setVisible(True) if self.isVertical(): self.logger.debug("Segment is vertical: %s", self.connection.segments.index(self)) self.endNode.parent.setPos( newPos.x(), self.endNode.parent.scenePos().y()) self.startNode.parent.setPos( newPos.x(), self.startNode.parent.scenePos().y()) self.updateGrad() if self.isHorizontal(): self.logger.debug("Segment is horizontal") self.endNode.parent.setPos( self.endNode.parent.scenePos().x(), newPos.y()) self.startNode.parent.setPos( self.startNode.parent.scenePos().x(), newPos.y()) elif type(self.endNode.parent ) is CornerItem and self.isVertical(): self.logger.debug("Segment is vertical and can't be moved") if self.isHorizontal(): isFirstSegment = hasattr( self.startNode.parent, "fromPort") and not self.startNode.prevN() isLastSegment = hasattr( self.endNode.parent, "fromPort") and not self.endNode.nextN() if isLastSegment: self.logger.debug("A last segment is being dragged.") if not self._isDraggingInProgress: self._initInMode1(False) self._dragInMode1(False, newPos) elif isFirstSegment: self.logger.debug("A first segment is being dragged.") if not self._isDraggingInProgress: self._initInMode1(True) self._dragInMode1(True, newPos) else: self.logger.debug( "Unrecognized editorMode in segmentItem mouseMoveEvent") def deleteNextHorizSeg(self, b, nextS): if b: pass else: nodeTodelete1 = self.endNode nodeTodelete2 = self.endNode.nextN() self.endNode = nextS.endNode self.startNode.setNext(self.endNode) self.endNode.setPrev(self.startNode) # x-position of the ending point of the next segment line posx1 = self.connection.segments[ self.connection.segments.index(self) + 2].line().p2().x() self.connection.parent.diagramScene.removeItem(nextS) self.connection.segments.remove(nextS) self.connection.parent.diagramScene.removeItem( nodeTodelete1.parent) indexOfSelf = self.connection.segments.index(self) nextVS = self.connection.segments[indexOfSelf + 1] self.connection.parent.diagramScene.removeItem(nextVS) self.connection.segments.remove(nextVS) self.connection.parent.diagramScene.removeItem( nodeTodelete2.parent) self.setLine( self.startNode.parent.scenePos().x(), self.startNode.parent.scenePos().y(), posx1, self.startNode.parent.scenePos().y(), ) def deletePrevHorizSeg(self, b, prevS): if b: pass else: nodeTodelete1 = self.startNode nodeTodelete2 = self.startNode.prevN() self.startNode = prevS.startNode self.startNode.setNext(self.endNode) self.endNode.setPrev(self.startNode) posx1 = self.connection.segments[ self.connection.segments.index(self) - 2].line().p1().x() self.connection.parent.diagramScene.removeItem(prevS) self.connection.segments.remove(prevS) self.connection.parent.diagramScene.removeItem( nodeTodelete1.parent) indexOfSelf = self.connection.segments.index(self) nextVS = self.connection.segments[indexOfSelf - 1] self.connection.parent.diagramScene.removeItem(nextVS) self.connection.segments.remove(nextVS) self.connection.parent.diagramScene.removeItem( nodeTodelete2.parent) self.setLine( posx1, self.endNode.parent.scenePos().y(), self.endNode.parent.scenePos().x(), self.endNode.parent.scenePos().y(), ) def deleteSegment(self): nodeToConnect = self.startNode.prevN() nodeToConnect2 = self.endNode.nextN() nodeToConnect.setNext(nodeToConnect2) self.connection.parent.diagramScene.removeItem(self) self.connection.segments.remove(self) self.connection.parent.diagramScene.removeItem(self.startNode.parent) self.connection.parent.diagramScene.removeItem(self.endNode.parent) def splitSegment(self): pass def mouseReleaseEvent(self, e): # Should be same as below # self.scene().removeItem(self) if e.button() == 1: self.keyPr = 0 if self.connection.parent.editorMode == 0: if self._isDraggingInProgress: self.cornerChild.setFlag( self.ItemSendsScenePositionChanges, True) self.hide() self.connection.segments.remove(self) self.connection.parent.diagramScene.removeItem(self) elif self.connection.parent.editorMode == 1: if self.isVertical(): try: self.oldX except AttributeError: pass else: command = HorizSegmentMoveCommand( self, self.oldX, "Moving segment command") self.connection.parent.parent().undoStack.push(command) self.oldX = self.scenePos().x() if self.isHorizontal(): if type(self.startNode.parent) is CornerItem and type( self.endNode.parent) is CornerItem: try: nextHorizSeg = self.connection.segments[ self.connection.segments.index(self) + 2] prevHorizSeg = self.connection.segments[ self.connection.segments.index(self) - 2] except IndexError: self.logger.debug("no next or prev segments") else: if nextHorizSeg.isHorizontal( ) and int(self.endNode.parent.pos().y() - 10) <= int(nextHorizSeg.line().p2().y( )) <= int(self.endNode.parent.pos().y() + 10): self.deleteNextHorizSeg(False, nextHorizSeg) self.logger.debug("next horizontal") return if prevHorizSeg.isHorizontal() and int( self.startNode.parent.pos().y() - 10 ) <= int(prevHorizSeg.line().p2().y()) <= int( self.startNode.parent.pos().y() + 10): self.deletePrevHorizSeg(False, prevHorizSeg) self.logger.debug("previous horizontal") return if self.secondCorner is not None: self.logger.debug("Second corner is not none") # if PortItem if hasattr(self.endNode.parent, "fromPort"): segbef = self.connection.segments[ self.connection.getNodePos( self.secondCorner.node.prevN().parent)] segbef.setLine( segbef.line().p1().x(), segbef.line().p1().y(), segbef.line().p2().x(), self.secondCorner.scenePos().y(), ) self.setLine( self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), self.line().p2().x(), self.line().p2().y(), ) self.secondCorner.setFlag( self.ItemSendsScenePositionChanges, True) self.thirdCorner.setFlag( self.ItemSendsScenePositionChanges, True) # Allow for iterative branching self.secondCorner = None self.thirdCorner = None self.firstLine = None self.secondLine = None self._isDraggingInProgress = False # if PortItem elif hasattr(self.startNode.parent, "fromPort"): segafter = self.connection.segments[ self.connection.getNodePos( self.thirdCorner.node.nextN().parent)] segafter.setLine( segafter.line().p1().x(), self.thirdCorner.scenePos().y(), segafter.line().p2().x(), segafter.line().p2().y(), ) self.setLine( self.line().p1().x(), self.line().p1().y(), self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), ) self.secondCorner.setFlag( self.ItemSendsScenePositionChanges, True) self.thirdCorner.setFlag( self.ItemSendsScenePositionChanges, True) # Allow for iterative branching self.secondCorner = None self.thirdCorner = None self.firstLine = None self.secondLine = None self._isDraggingInProgress = False else: self.logger.debug("getting no start or end") else: self.logger.debug("Second corner is none") else: pass def initInMode0(self): if (hasattr(self.startNode.parent, "fromPort")) and (self.startNode.prevN() is not None): self.disrAfterNode = self.startNode self.start = self.startNode.prevN().prevN() segments = self.connection.segments for s in segments: if s.startNode is self.start: self.disrBeforeSeg = s self.disrAfterSeg = self self.disrBefore = True else: self.start = self.startNode if (hasattr(self.endNode.parent, "fromPort")) and (self.endNode.nextN() is not None): self.disrBeforeNode = self.endNode self.end = self.endNode.nextN().nextN() segments = self.connection.segments for s in segments: if s.endNode is self.end: self.disrAfterSeg = s self.disrBeforeSeg = self self.disrAfter = True else: self.end = self.endNode rad = self.connection.getRadius() self.cornerChild = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.start, self.end, self.connection) self.firstChild = self._createSegment(self.start, self.cornerChild.node) self.secondChild = self._createSegment(self.cornerChild.node, self.end) self.start.setNext(self.cornerChild.node) self.end.setPrev(self.cornerChild.node) self.firstChild.setVisible(False) self.secondChild.setVisible(False) self.cornerChild.setVisible(False) self.connection.parent.diagramScene.addItem(self.firstChild) self.connection.parent.diagramScene.addItem(self.secondChild) self.connection.parent.diagramScene.addItem(self.cornerChild) self._isDraggingInProgress = True def _initInMode1(self, b): rad = self.connection.getRadius() if b: if (hasattr(self.startNode.parent, "fromPort")) and (self.startNode.prevN() is None): # We are at the toPort. # self.end = self.endNode # self.start = self.startNode self.secondCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.startNode, None, self.connection) self.thirdCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.secondCorner.node, self.endNode, self.connection) self.secondCorner.node.setNext(self.thirdCorner.node) self.startNode.setNext(self.secondCorner.node) self.endNode.setPrev(self.thirdCorner.node) self.endNode = self.secondCorner.node self.firstLine = self._createSegment(self.secondCorner.node, self.thirdCorner.node) self.secondLine = self._createSegment( self.thirdCorner.node, self.thirdCorner.node.nextN()) self.secondCorner.setVisible(False) self.thirdCorner.setVisible(False) self.firstLine.setVisible(False) self.secondLine.setVisible(False) # self.thirdLine.setVisible(False) self.connection.parent.diagramScene.addItem(self.secondCorner) self.connection.parent.diagramScene.addItem(self.thirdCorner) self.connection.parent.diagramScene.addItem(self.firstLine) self.connection.parent.diagramScene.addItem(self.secondLine) self.logger.debug("inited") self._isDraggingInProgress = True else: if (hasattr(self.endNode.parent, "fromPort")) and (self.endNode.nextN() is None): # We are at the toPort. # self.end = self.endNode # self.start = self.startNode self.secondCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.startNode, None, self.connection) self.thirdCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.secondCorner.node, self.endNode, self.connection) self.secondCorner.node.setNext(self.thirdCorner.node) self.startNode.setNext(self.secondCorner.node) self.endNode.setPrev(self.thirdCorner.node) self.startNode = self.thirdCorner.node self.firstLine = self._createSegment( self.secondCorner.node.prevN(), self.secondCorner.node) self.secondLine = self._createSegment(self.secondCorner.node, self.thirdCorner.node) self.secondCorner.setVisible(False) self.thirdCorner.setVisible(False) self.firstLine.setVisible(False) self.secondLine.setVisible(False) # self.thirdLine.setVisible(False) self.connection.parent.diagramScene.addItem(self.secondCorner) self.connection.parent.diagramScene.addItem(self.thirdCorner) self.connection.parent.diagramScene.addItem(self.firstLine) self.connection.parent.diagramScene.addItem(self.secondLine) self.logger.debug("inited") self._isDraggingInProgress = True def _createSegment(self, startNode, endNode) -> "SegmentItemBase": raise NotImplementedError() def isVertical(self): return self.line().p1().x() == self.line().p2().x() def isHorizontal(self): return self.line().p1().y() == self.line().p2().y() def dragInMode0(self, newPos): p1 = self.line().p1() p2 = self.line().p2() if len(self.scene().items(newPos)) == 0: self.firstChild.setLine(p1.x(), p1.y(), newPos.x(), newPos.y()) self.secondChild.setLine(newPos.x(), newPos.y(), p2.x(), p2.y()) self.cornerChild.setPos(newPos) self.firstChild.updateGrad() self.secondChild.updateGrad() # Bring corner to front self.cornerChild.setZValue(100) self.firstChild.setZValue(1) self.secondChild.setZValue(1) self.firstChild.setVisible(True) self.secondChild.setVisible(True) self.cornerChild.setVisible(True) def _dragInMode1(self, b, newPos): self.logger.debug("after inited") if b: self.thirdCorner.setPos(newPos.x() - 10, newPos.y()) self.secondCorner.setPos(newPos.x() - 10, self.connection.fromPort.scenePos().y()) self.thirdCorner.node.nextN().parent.setY(newPos.y()) self.firstLine.setLine( self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), self.thirdCorner.scenePos().x(), newPos.y(), ) self.secondLine.setLine( self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), self.thirdCorner.node.nextN().parent.scenePos().x(), self.thirdCorner.node.nextN().parent.scenePos().y(), ) self.setLine( self.startNode.parent.fromPort.scenePos().x(), self.startNode.parent.fromPort.scenePos().y(), self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), ) self.secondCorner.setZValue(100) self.thirdCorner.setZValue(100) self.firstLine.setZValue(1) self.secondLine.setZValue(1) self.secondCorner.setVisible(True) self.thirdCorner.setVisible(True) self.firstLine.setVisible(True) self.secondLine.setVisible(True) else: self.secondCorner.setPos(newPos.x() + 10, newPos.y()) self.thirdCorner.setPos(newPos.x() + 10, self.connection.toPort.scenePos().y()) self.secondCorner.node.prevN().parent.setY(newPos.y()) self.firstLine.setLine( self.secondCorner.node.prevN().parent.scenePos().x(), newPos.y(), self.secondCorner.scenePos().x(), newPos.y(), ) self.secondLine.setLine( self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), ) self.setLine( self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), self.endNode.parent.toPort.scenePos().x(), self.endNode.parent.toPort.scenePos().y(), ) self.secondCorner.setZValue(100) self.thirdCorner.setZValue(100) self.firstLine.setZValue(1) self.secondLine.setZValue(1) self.secondCorner.setVisible(True) self.thirdCorner.setVisible(True) self.firstLine.setVisible(True) self.secondLine.setVisible(True) def renameConn(self): self.scene().parent().showSegmentDlg(self) def printItemsAt(self): self.logger.debug("Items at startnode are %s", str(self.scene().items(self.line().p1()))) self.logger.debug("Items at endnode are %s", str(self.scene().items(self.line().p2()))) for s in self.connection.segments: self.logger.debug( "Segment in list is %s has startnode %s endnode %s", str(s), str(s.startNode.parent), str(s.endNode.parent), ) def contextMenuEvent(self, event): menu = self._getContextMenu() menu.exec(event.screenPos()) def _getContextMenu(self) -> QMenu: menu = QMenu() a1 = menu.addAction("Rename...") a1.triggered.connect(self.renameConn) a2 = menu.addAction("Delete this connection") a2.triggered.connect( self.connection.createDeleteUndoCommandAndAddToStack) a3 = menu.addAction("Invert this connection") a3.triggered.connect(self.connection.invertConnection) a4 = menu.addAction("Toggle name") a4.triggered.connect(self.connection.toggleLabelVisible) a5 = menu.addAction("Toggle mass flow") a5.triggered.connect(self.connection.toggleMassFlowLabelVisible) return menu def setLabelVisible(self, isVisible: bool) -> None: self.label.setVisible(isVisible) def toggleLabelVisible(self) -> None: wasVisible = self.label.isVisible() self.setLabelVisible(not wasVisible) def setMassFlowLabelVisible(self, isVisible: bool) -> None: self.labelMass.setVisible(isVisible) def toggleMassFlowLabelVisible(self) -> None: wasVisible = self.labelMass.isVisible() self.setMassFlowLabelVisible(not wasVisible) def setSelect(self, isSelected: bool) -> None: raise NotImplementedError() @staticmethod def _createSelectPen() -> QPen: color = QColor(125, 242, 189) width = 4 selectPen = QPen(color, width) return selectPen def setColorAndWidthAccordingToMassflow(self, color, width): raise NotImplementedError()
class TVentil(BlockItem, MassFlowNetworkContributorMixin): def __init__(self, trnsysType, parent, **kwargs): super(TVentil, self).__init__(trnsysType, parent, **kwargs) self.h = 40 self.w = 40 self.isTempering = False self.positionForMassFlowSolver = 1.0 self.posLabel = QGraphicsTextItem(str(self.positionForMassFlowSolver), self) self.posLabel.setVisible(False) self.inputs.append(_cspi.createSinglePipePortItem("i", 0, self)) self.inputs.append(_cspi.createSinglePipePortItem("i", 1, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) self.changeSize() def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.T_VENTIL_SVG def changeSize(self): w = self.w h = self.h delta = 20 # Limit the block size: if h < 20: h = 20 if w < 40: w = 40 # center label: rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (w - lw) / 2 self.label.setPos(lx, h - self.flippedV * (h + h / 2)) self.posLabel.setPos(lx + 5, -15) self.origInputsPos = [[0, delta], [delta, 0]] self.origOutputsPos = [[w, delta]] self.inputs[0].setPos(self.origInputsPos[0][0], self.origInputsPos[0][1]) self.inputs[1].setPos(self.origInputsPos[1][0], self.origInputsPos[1][1]) self.outputs[0].setPos(self.origOutputsPos[0][0], self.origOutputsPos[0][1]) self.updateFlipStateH(self.flippedH) self.updateFlipStateV(self.flippedV) self.inputs[0].side = (self.rotationN + 2 * self.flippedH) % 4 self.inputs[1].side = (self.rotationN + 1 + 2 * self.flippedV) % 4 self.outputs[0].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 return w, h def rotateBlockCW(self): super().rotateBlockCW() # Rotate valve position label back so it will always stay horizontal self._updateRotation() def rotateBlockCCW(self): super().rotateBlockCCW() # Rotate valve position label back so it will always stay horizontal self._updateRotation() def resetRotation(self): super().resetRotation() # Rotate valve position label back so it will always stay horizontal self._updateRotation() def _updateRotation(self): self.posLabel.setRotation(-self.rotationN * 90) def setComplexDiv(self, b): self.isTempering = bool(b) def setPositionForMassFlowSolver(self, f): self.positionForMassFlowSolver = f def encode(self): dictName, dct = super(TVentil, self).encode() dct["IsTempering"] = self.isTempering dct["PositionForMassFlowSolver"] = self.positionForMassFlowSolver return dictName, dct def decode(self, i, resBlockList): super().decode(i, resBlockList) if "IsTempering" not in i or "PositionForMassFlowSolver" not in i: self.logger.debug("Old version of diagram") self.positionForMassFlowSolver = 1.0 else: self.isTempering = i["IsTempering"] self.positionForMassFlowSolver = i["PositionForMassFlowSolver"] def decodePaste(self, i, offset_x, offset_y, resConnList, resBlockList, **kwargs): super(TVentil, self).decodePaste(i, offset_x, offset_y, resConnList, resBlockList, **kwargs) if "IsTempering" or "PositionForMassFlowSolver" not in i: self.logger.debug("Old version of diagram") self.positionForMassFlowSolver = 1.0 else: self.isTempering = i["IsTempering"] self.positionForMassFlowSolver = i["PositionForMassFlowSolver"] def exportMassFlows(self): if not self.isTempering: resStr = "xFrac" + self.displayName + " = " + str( self.positionForMassFlowSolver) + "\n" equationNr = 1 return resStr, equationNr else: return "", 0 def exportDivSetting1(self): if self.isTempering: constants = 1 f = "T_set_" + self.displayName + "=50\n" return f, constants else: return "", 0 def exportDivSetting2(self, nUnit): if self.isTempering: f = "" nUnit = nUnit + 1 f += "UNIT %d TYPE 811 ! Passive Divider for heating \n" % nUnit f += "PARAMETERS 1" + "\n" f += "5 !Nb.of iterations before fixing the value \n" f += "INPUTS 4 \n" if (self.outputs[0].pos().y() == self.inputs[0].pos().y() or self.outputs[0].pos().x() == self.inputs[0].pos().x()): first = self.inputs[0] second = self.inputs[1] f += "T" + first.connectionList[0].displayName + "\n" f += "T" + second.connectionList[0].displayName + "\n" f += "Mfr" + self.outputs[0].connectionList[0].displayName + "\n" f += "T_set_" + self.displayName + "\n" f += "*** INITIAL INPUT VALUES" + "\n" f += "35.0 21.0 800.0 T_set_" + self.displayName + "\n" f += "EQUATIONS 1\n" f += "xFrac" + self.displayName + " = 1.-[%d,5] \n\n" % nUnit return f, nUnit else: return "", nUnit def getInternalPiping(self) -> _mfs.InternalPiping: teePiece, modelPortItemsToGraphicalPortItem = self._getModelAndMapping( ) return _mfs.InternalPiping([teePiece], modelPortItemsToGraphicalPortItem) def _getModelAndMapping(self): input1 = _mfn.PortItem("TVentil Input 1", _mfn.PortItemType.INPUT) input2 = _mfn.PortItem("TVentil Input 2", _mfn.PortItemType.INPUT) output = _mfn.PortItem("TVentil Output", _mfn.PortItemType.OUTPUT) teePiece = _mfn.Diverter(self.displayName, self.trnsysId, output, input1, input2) modelPortItemsToGraphicalPortItem = { input1: self.inputs[0], input2: self.inputs[1], output: self.outputs[0] } return teePiece, modelPortItemsToGraphicalPortItem def exportPipeAndTeeTypesForTemp(self, startingUnit): if self.isVisible(): f = "" unitNumber = startingUnit tNr = 929 # Temperature calculation from a tee-piece unitText = "" ambientT = 20 equationConstant = 1 unitText += "UNIT " + str(unitNumber) + " TYPE " + str(tNr) + "\n" unitText += "!" + self.displayName + "\n" unitText += "PARAMETERS 0\n" unitText += "INPUTS 6\n" openLoops, nodesToIndices = self._getOpenLoopsAndNodeToIndices() assert len(openLoops) == 1 openLoop = openLoops[0] assert len(openLoop.realNodes) == 1 realNode = openLoop.realNodes[0] outputVariables = realNode.serialize( nodesToIndices).outputVariables for outputVariable in outputVariables: if not outputVariable: continue unitText += outputVariable.name + "\n" unitText += f"T{self.outputs[0].connectionList[0].displayName}\n" unitText += f"T{self.inputs[0].connectionList[0].displayName}\n" unitText += f"T{self.inputs[1].connectionList[0].displayName}\n" unitText += "***Initial values\n" unitText += 3 * "0 " + 3 * (str(ambientT) + " ") + "\n" unitText += "EQUATIONS 1\n" unitText += "T" + self.displayName + "= [" + str( unitNumber) + "," + str(equationConstant) + "]\n" unitNumber += 1 f += unitText + "\n" return f, unitNumber else: return "", startingUnit