def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ if self.parent.parent().projectPath == "": self.path = self.parent.parent().projectFolder else: self.path = self.parent.parent().projectPath pathName = self.displayName self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree)
def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ self.logger.debug(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": # self.path = os.path.dirname(__file__) # self.path = os.path.join(self.path, 'default') self.path = self.parent.parent().projectFolder # now = datetime.now() # self.fileName = now.strftime("%Y%m%d%H%M%S") # self.path = os.path.join(self.path, self.fileName) else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree)
def createConfigBrowser(self, loadPath): self.layoutToRemove = self.findChild(QHBoxLayout, "Config_Layout") try: # treeToRemove.hide() self.layoutToRemove.deleteLater() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") runConfigData = self._getPackageResourceData("templates/run.config") runConfigPath = _pl.Path(loadPath) / "run.config" runConfigPath.write_bytes(runConfigData) self.HBox = QHBoxLayout() self.refreshButton = QPushButton(self) self.refreshButton.setIcon(_img.ROTATE_TO_RIGHT_PNG.icon()) self.refreshButton.clicked.connect(self.refreshConfig) self.model = MyQFileSystemModel() self.model.setRootPath(loadPath) self.model.setName("Config File") self.model.setFilter(QDir.Files) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(loadPath)) self.tree.setObjectName("config") self.tree.setFixedHeight(60) self.tree.setSortingEnabled(False) self.HBox.addWidget(self.refreshButton) self.HBox.addWidget(self.tree) self.HBox.setObjectName("Config_Layout") self.fileBrowserLayout.addLayout(self.HBox)
def createDdckTree(self, loadPath): treeToRemove = self.findChild(QTreeView, "ddck") try: # treeToRemove.hide() treeToRemove.deleteLater() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") if self.projectPath == "": loadPath = os.path.join(loadPath, "ddck") if not os.path.exists(loadPath): os.makedirs(loadPath) self.model = MyQFileSystemModel() self.model.setRootPath(loadPath) self.model.setName("ddck") self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(loadPath)) self.tree.setObjectName("ddck") self.tree.setMinimumHeight(600) self.tree.setSortingEnabled(True) self.splitter.insertWidget(0, self.tree)
class HPDoubleDual(BlockItem, MassFlowNetworkContributorMixin): def __init__(self, trnsysType, parent, **kwargs): super(HPDoubleDual, self).__init__(trnsysType, parent, **kwargs) self.inputs.append(_cspi.createSinglePipePortItem("i", 0, self)) self.inputs.append(_cspi.createSinglePipePortItem("i", 2, self)) self.inputs.append(_cspi.createSinglePipePortItem("i", 2, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 0, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) self.loadedFiles = [] # For restoring correct order of trnsysObj list self.childIds = [] self.childIds.append(self.trnsysId) self.childIds.append(self.parent.parent().idGen.getTrnsysID()) self.childIds.append(self.parent.parent().idGen.getTrnsysID()) self.changeSize() self.addTree() def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.HP_DOUBLE_DUAL_SVG def changeSize(self): w = self.w h = self.h """ Resize block function """ 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.origInputsPos = [[0, h - 2 * delta], [w, 2 * delta], [w, h - delta]] # inlet of [evap, cond, cond] self.origOutputsPos = [[0, h - delta], [w, delta], [w, h - 2 * delta]] # outlet of [evap, cond, cond] self.inputs[0].setPos(self.origInputsPos[0][0], self.origInputsPos[0][1]) #evaporator self.inputs[1].setPos(self.origInputsPos[1][0], self.origInputsPos[1][1]) # top condenser self.inputs[2].setPos(self.origInputsPos[2][0], self.origInputsPos[2][1]) # bottom condenser self.outputs[0].setPos(self.origOutputsPos[0][0], self.origOutputsPos[0][1]) #evaporator self.outputs[1].setPos(self.origOutputsPos[1][0], self.origOutputsPos[1][1]) # top condenser self.outputs[2].setPos(self.origOutputsPos[2][0], self.origOutputsPos[2][1]) # bottom condenser self.updateFlipStateH(self.flippedH) self.updateFlipStateV(self.flippedV) self.inputs[0].side = (self.rotationN + 2 * self.flippedH) % 4 self.inputs[1].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 self.inputs[2].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 self.outputs[0].side = (self.rotationN + 2 * self.flippedH) % 4 self.outputs[1].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 self.outputs[2].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 return w, h def encode(self): if self.isVisible(): self.logger.debug("Encoding a HeatPump") portListInputs = [] portListOutputs = [] for p in self.inputs: portListInputs.append(p.id) for p in self.outputs: portListOutputs.append(p.id) dct = {} dct[".__BlockDict__"] = True dct["BlockName"] = self.name dct["BlockDisplayName"] = self.displayName dct["PortsIDIn"] = portListInputs dct["PortsIDOut"] = portListOutputs dct["HeatPumpPosition"] = (float(self.pos().x()), float(self.pos().y())) dct["ID"] = self.id dct["trnsysID"] = self.trnsysId dct["childIds"] = self.childIds dct["FlippedH"] = self.flippedH dct["FlippedV"] = self.flippedH dct["RotationN"] = self.rotationN dictName = "Block-" return dictName, dct def decode(self, i, resBlockList): self.flippedH = i["FlippedH"] self.flippedV = i["FlippedV"] self.childIds = i["childIds"] self.displayName = i["BlockDisplayName"] self.changeSize() for x in range(len(self.inputs)): self.inputs[x].id = i["PortsIDIn"][x] self.logger.debug("Input at heatExchanger") for x in range(len(self.outputs)): self.outputs[x].id = i["PortsIDOut"][x] self.logger.debug("Output at heatExchanger") self.setPos(float(i["HeatPumpPosition"][0]), float(i["HeatPumpPosition"][1])) self.trnsysId = i["trnsysID"] self.id = i["ID"] resBlockList.append(self) def decodePaste(self, i, offset_x, offset_y, resConnList, resBlockList, **kwargs): self.flippedH = i["FlippedH"] self.flippedV = i["FlippedV"] self.changeSize() for x in range(len(self.inputs)): self.inputs[x].id = i["PortsIDIn"][x] self.logger.debug("Input at heatExchanger") for x in range(len(self.outputs)): self.outputs[x].id = i["PortsIDOut"][x] self.logger.debug("Output at heatExchanger") self.setPos(float(i["HeatPumpPosition"][0] + offset_x), float(i["HeatPumpPosition"][1] + offset_y)) # self.trnsysId = i["trnsysID"] # self.id = i["ID"] resBlockList.append(self) def exportBlackBox(self): equation = [] files = glob.glob(os.path.join(self.path, "**/*.ddck"), recursive=True) if not (files): status = "noDdckFile" for i in range(1, 3): equation.append("T" + self.displayName + "X" + str(i) + "=1") 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(): counter = 1 for j in range(i, len(lines) - i): if lines[j][0] == "T": outputT = lines[j].split("=")[0].replace(" ", "") equation.append("T" + self.displayName + "X" + str(counter) + "=1 ! suggestion: " + outputT) counter += 1 if counter == 4: status = "success" break break return status, equation def getInternalPiping(self) -> InternalPiping: pipes = [] portItems = {} for i in range(3): inputPort = _mfn.PortItem(f"Input {i+1}", _mfn.PortItemType.INPUT) outputPort = _mfn.PortItem(f"Output {i+1}", _mfn.PortItemType.OUTPUT) pipe = _mfn.Pipe(f"{self.displayName}Side{i+1}", self.childIds[i], inputPort, outputPort) pipes.append(pipe) portItems[inputPort] = self.inputs[i] portItems[outputPort] = self.outputs[i] return InternalPiping(pipes, portItems) def getSubBlockOffset(self, c): for i in range(3): if ( self.inputs[i] == c.toPort or self.inputs[i] == c.fromPort or self.outputs[i] == c.toPort or self.outputs[i] == c.fromPort ): return i def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ self.logger.debug(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": # self.path = os.path.dirname(__file__) # self.path = os.path.join(self.path, 'default') self.path = self.parent.parent().projectFolder # now = datetime.now() # self.fileName = now.strftime("%Y%m%d%H%M%S") # self.path = os.path.join(self.path, self.fileName) else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # self.logger.debug("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) # self.logger.debug("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild(QTreeView, self.displayName + "Tree") shutil.rmtree(self.path) self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) self.logger.debug(os.path.dirname(self.path)) # destPath = str(os.path.dirname(self.path))+'\\HPTwoHx_'+self.displayName destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)
class IceStorageTwoHx(BlockItemFourPorts): def __init__(self, trnsysType, parent, **kwargs): super(IceStorageTwoHx, self).__init__(trnsysType, parent, **kwargs) self.loadedFiles = [] self.addTree() self.changeSize() def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.ICE_STORAGE_TWO_HX_SVG def changeSize(self): w = self.w h = self.h """ Resize block function """ delta = 20 # center label: rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (w - lw) / 2 self.label.setPos(lx, h) self.origInputsPos = [[0, delta], [w, delta]] self.origOutputsPos = [[0, h - delta], [w, h - 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.outputs[1].setPos(self.origOutputsPos[1][0], self.origOutputsPos[1][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 + 2 - 2 * self.flippedH) % 4 self.outputs[0].side = (self.rotationN + 2 * self.flippedH) % 4 self.outputs[1].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 return w, h def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ self.logger.debug(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": # self.path = os.path.dirname(__file__) # self.path = os.path.join(self.path, 'default') self.path = self.parent.parent().projectFolder # now = datetime.now() # self.fileName = now.strftime("%Y%m%d%H%M%S") # self.path = os.path.join(self.path, self.fileName) else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # self.logger.debug("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) # self.logger.debug("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild(QTreeView, self.displayName + "Tree") shutil.rmtree(self.path) self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) self.logger.debug(os.path.dirname(self.path)) # destPath = str(os.path.dirname(self.path))+'\\IceStorage_'+self.displayName destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)
class Collector(BlockItem, MassFlowNetworkContributorMixin): def __init__(self, trnsysType, parent, **kwargs): super().__init__(trnsysType, parent, **kwargs) self.inputs.append(_cspi.createSinglePipePortItem("i", 2, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) self.loadedFiles = [] self.changeSize() self.addTree() def getInternalPiping(self) -> InternalPiping: inputPort = _mfn.PortItem("Collector Input", _mfn.PortItemType.INPUT) outputPort = _mfn.PortItem("Collector Output", _mfn.PortItemType.OUTPUT) pipe = _mfn.Pipe(self.displayName, self.trnsysId, inputPort, outputPort) return InternalPiping([pipe], { inputPort: self.inputs[0], outputPort: self.outputs[0] }) def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.COLLECTOR_SVG def changeSize(self): w = self.w h = self.h delta = 20 if h < 20: h = 20 if w < 40: w = 40 rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (w - lw) / 2 self.label.setPos(lx, h) self.origInputsPos = [[w, h - delta]] self.origOutputsPos = [[w, delta]] self.outputs[0].setPos(self.origOutputsPos[0][0], self.origOutputsPos[0][1]) self.inputs[0].setPos(self.origInputsPos[0][0], self.origInputsPos[0][1]) self.updateFlipStateH(self.flippedH) self.updateFlipStateV(self.flippedV) self.inputs[0].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 self.outputs[0].side = (self.rotationN + 2 - 2 * self.flippedH) % 4 return w, h def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ if self.parent.parent().projectPath == "": self.path = self.parent.parent().projectFolder else: self.path = self.parent.parent().projectPath pathName = self.displayName self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # self.logger.debug("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) # self.logger.debug("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild( QTreeView, self.displayName + "Tree") try: shutil.rmtree(self.path) except: pass self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) self.logger.debug(os.path.dirname(self.path)) # destPath = str(os.path.dirname(self.path))+'\\Collector_'+self.displayName destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)
class PV(BlockItem): def __init__(self, trnsysType, parent, **kwargs): super(PV, self).__init__(trnsysType, parent, **kwargs) factor = 0.97 self.w = 100 self.h = 100 self.loadedFiles = [] self.changeSize() self.addTree() def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.PV_SVG def changeSize(self): w = self.w h = self.h # 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) return w, h def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ self.logger.debug(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": self.path = self.parent.parent().projectFolder else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # self.logger.debug("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) # self.logger.debug("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild(QTreeView, self.displayName + "Tree") shutil.rmtree(self.path) self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) self.logger.debug(os.path.dirname(self.path)) destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)
class Editor(QWidget): """ This class is the central widget of the MainWindow. It contains the items library, diagram graphics scene and graphics view, and the inspector widget Function of Connections: Logically: A Connection is composed of a fromPort and a toPort, which gives the direction of the pipe. Ports are attached to Blocks. Visually: A diagram editor has a QGraphicsLineItem (connLineItem) which is set Visible only when a connection is being created Function of BlockItems: Items can be added to the library by adding them to the model of the library broswer view. Then they can be dragged and dropped into the diagram view. Function of trnsysExport: When exporting the trnsys file, exportData() is called. Function of save and load: A diagram can be saved to a json file by calling encodeDiagram and can then be loaded by calling decodeDiagram wiht appropiate filenames. Attributes ---------- projectFolder : str Path to the folder of the project diagramName : str Name used for saving the diagram saveAsPath : :obj:`Path` Default saving location is trnsysGUI/diagrams, path only set if "save as" used idGen : :obj:`IdGenerator` Is used to distribute ids (id, trnsysId(for trnsysExport), etc) alignMode : bool Enables mode in which a dragged block is aligned to y or x value of another one Toggled in the MainWindow class in toggleAlignMode() editorMode : int Mode 0: Pipes are PolySun-like Mode 1: Pipes have only 90deg angles, visio-like snapGrid : bool Enable/Disable align grid snapSize : int Size of align grid horizontalLayout : :obj:`QHBoxLayout` Contains the diagram editor and the layout containing the library browser view and the listview vertL : :obj:`QVBoxLayout` Cointains the library browser view and the listWidget moveDirectPorts: bool Enables/Disables moving direct ports of storagetank (doesn't work with HxPorts yet) diagramScene : :obj:`QGraphicsScene` Contains the "logical" part of the diagram diagramView : :obj:`QGraphicsView` Contains the visualization of the diagramScene _currentlyDraggedConnectionFromPort : :obj:`PortItem` connectionList : :obj:`List` of :obj:`Connection` trnsysObj : :obj:`List` of :obj:`BlockItem` and :obj:`Connection` graphicalObj : :obj:`List` of :obj:`GraphicalItem` connLine : :obj:`QLineF` connLineItem = :obj:`QGraphicsLineItem` """ def __init__(self, parent, projectFolder, jsonPath, loadValue, logger): super().__init__(parent) self.logger = logger self.logger.info("Initializing the diagram editor") self.projectFolder = projectFolder self.diagramName = os.path.split(self.projectFolder)[-1] + ".json" self.saveAsPath = _pl.Path() self.idGen = IdGenerator() self.testEnabled = False self.existReference = True self.controlExists = 0 self.controlDirectory = "" self.alignMode = False self.moveDirectPorts = False self.editorMode = 1 # Related to the grid blocks can snap to self.snapGrid = False self.snapSize = 20 self.trnsysPath = _pl.Path(r"C:\Trnsys17\Exe\TRNExe.exe") self.horizontalLayout = QHBoxLayout(self) self.libraryBrowserView = QListView(self) self.libraryModel = LibraryModel(self) self.libraryBrowserView.setGridSize(QSize(65, 65)) self.libraryBrowserView.setResizeMode(QListView.Adjust) self.libraryModel.setColumnCount(0) componentNamesWithIcon = [ ("Connector", _img.CONNECTOR_SVG.icon()), ("TeePiece", _img.TEE_PIECE_SVG.icon()), ("DPTee", _img.DP_TEE_PIECE_SVG.icon()), ("SPCnr", _img.SINGLE_DOUBLE_PIPE_CONNECTOR_SVG.icon()), ("DPCnr", _img.DOUBLE_DOUBLE_PIPE_CONNECTOR_SVG.icon()), ("TVentil", _img.T_VENTIL_SVG.icon()), ("WTap_main", _img.W_TAP_MAIN_SVG.icon()), ("WTap", _img.W_TAP_SVG.icon()), ("Pump", _img.PUMP_SVG.icon()), ("Collector", _img.COLLECTOR_SVG.icon()), ("GroundSourceHx", _img.GROUND_SOURCE_HX_SVG.icon()), ("PV", _img.PV_SVG.icon()), ("HP", _img.HP_SVG.icon()), ("HPTwoHx", _img.HP_TWO_HX_SVG.icon()), ("HPDoubleDual", _img.HP_DOUBLE_DUAL_SVG.icon()), ("HPDual", _img.HP_DUAL_SVG.icon()), ("AirSourceHP", _img.AIR_SOURCE_HP_SVG.icon()), ("StorageTank", _img.STORAGE_TANK_SVG.icon()), ("IceStorage", _img.ICE_STORAGE_SVG.icon()), ("PitStorage", _img.PIT_STORAGE_SVG.icon()), ("IceStorageTwoHx", _img.ICE_STORAGE_TWO_HX_SVG.icon()), ("ExternalHx", _img.EXTERNAL_HX_SVG.icon()), ("Radiator", _img.RADIATOR_SVG.icon()), ("Boiler", _img.BOILER_SVG.icon()), ("Sink", _img.SINK_SVG.icon()), ("Source", _img.SOURCE_SVG.icon()), ("SourceSink", _img.SOURCE_SINK_SVG.icon()), ("Geotherm", _img.GEOTHERM_SVG.icon()), ("Water", _img.WATER_SVG.icon()), ("Crystalizer", _img.CRYSTALIZER_SVG.icon()), ("GenericBlock", _img.GENERIC_BLOCK_PNG.icon()), ("GraphicalItem", _img.GENERIC_ITEM_PNG.icon()), ] libItems = [ QtGui.QStandardItem(icon, name) for name, icon in componentNamesWithIcon ] for i in libItems: self.libraryModel.appendRow(i) self.libraryBrowserView.setModel(self.libraryModel) self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode) self.libraryBrowserView.setDragDropMode( self.libraryBrowserView.DragOnly) self.diagramScene = Scene(self) self.diagramView = View(self.diagramScene, self) # For list view self.vertL = QVBoxLayout() self.vertL.addWidget(self.libraryBrowserView) self.vertL.setStretchFactor(self.libraryBrowserView, 2) self.listV = QListWidget() self.vertL.addWidget(self.listV) self.vertL.setStretchFactor(self.listV, 1) # for file browser self.projectPath = "" self.fileList = [] if loadValue == "new" or loadValue == "json": self.createProjectFolder() self.fileBrowserLayout = QVBoxLayout() self.pathLayout = QHBoxLayout() self.projectPathLabel = QLabel("Project Path:") self.PPL = QLineEdit(self.projectFolder) self.PPL.setDisabled(True) self.pathLayout.addWidget(self.projectPathLabel) self.pathLayout.addWidget(self.PPL) self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) self.splitter = QSplitter(Qt.Vertical, ) self.splitter.setChildrenCollapsible(False) self.scroll.setWidget(self.splitter) self.scroll.setFixedWidth(350) self.fileBrowserLayout.addLayout(self.pathLayout) self.fileBrowserLayout.addWidget(self.scroll) self.createDdckTree(self.projectFolder) if loadValue == "new" or loadValue == "json": self.createConfigBrowser(self.projectFolder) self.copyGenericFolder(self.projectFolder) self.createHydraulicDir(self.projectFolder) self.createWeatherAndControlDirs(self.projectFolder) self.horizontalLayout.addLayout(self.vertL) self.horizontalLayout.addWidget(self.diagramView) self.horizontalLayout.addLayout(self.fileBrowserLayout) self.horizontalLayout.setStretchFactor(self.diagramView, 5) self.horizontalLayout.setStretchFactor(self.libraryBrowserView, 1) self._currentlyDraggedConnectionFromPort = None self.connectionList = [] self.trnsysObj = [] self.graphicalObj = [] self.fluids = _hlm.Fluids([]) self.hydraulicLoops = _hlm.HydraulicLoops([]) self.copyGroupList = QGraphicsItemGroup() self.selectionGroupList = QGraphicsItemGroup() self.printerUnitnr = 0 # Different colors for connLineColor colorsc = "red" linePx = 4 if colorsc == "red": connLinecolor = QColor(Qt.red) elif colorsc == "blueish": connLinecolor = QColor(3, 124, 193) # Blue elif colorsc == "darkgray": connLinecolor = QColor(140, 140, 140) # Gray else: connLinecolor = QColor(196, 196, 196) # Gray # Only for displaying on-going creation of connection self.connLine = QLineF() self.connLineItem = QGraphicsLineItem(self.connLine) self.connLineItem.setPen(QtGui.QPen(connLinecolor, linePx)) self.connLineItem.setVisible(False) self.diagramScene.addItem(self.connLineItem) # For line that shows quickly up when using the align mode self.alignYLine = QLineF() self.alignYLineItem = QGraphicsLineItem(self.alignYLine) self.alignYLineItem.setPen(QtGui.QPen(QColor(196, 249, 252), 2)) self.alignYLineItem.setVisible(False) self.diagramScene.addItem(self.alignYLineItem) # For line that shows quickly up when using align mode self.alignXLine = QLineF() self.alignXLineItem = QGraphicsLineItem(self.alignXLine) self.alignXLineItem.setPen(QtGui.QPen(QColor(196, 249, 252), 2)) self.alignXLineItem.setVisible(False) self.diagramScene.addItem(self.alignXLineItem) if loadValue == "load" or loadValue == "copy": self._decodeDiagram(os.path.join(self.projectFolder, self.diagramName), loadValue=loadValue) elif loadValue == "json": self._decodeDiagram(jsonPath, loadValue=loadValue) # Debug function def dumpInformation(self): self.logger.debug("Diagram information:") self.logger.debug("Mode is " + str(self.editorMode)) self.logger.debug("Next ID is " + str(self.idGen.getID())) self.logger.debug("Next cID is " + str(self.idGen.getConnID())) self.logger.debug("TrnsysObjects are:") for t in self.trnsysObj: self.logger.debug(str(t)) self.logger.debug("") self.logger.debug("Scene items are:") sItems = self.diagramScene.items() for it in sItems: self.logger.info(str(it)) self.logger.debug("") for c in self.connectionList: c.printConn() self.logger.debug("") # Connections related methods def startConnection(self, port): self._currentlyDraggedConnectionFromPort = port def _createConnection(self, startPort, endPort) -> None: if startPort is not endPort: if (isinstance(startPort.parent, StorageTank) and isinstance(endPort.parent, StorageTank) and startPort.parent != endPort.parent): msgSTank = QMessageBox(self) msgSTank.setText( "Storage Tank to Storage Tank connection is not working atm!" ) msgSTank.exec_() isValidSinglePipeConnection = isinstance( startPort, SinglePipePortItem) and isinstance( endPort, SinglePipePortItem) if isValidSinglePipeConnection: command = CreateSinglePipeConnectionCommand( startPort, endPort, self) elif isinstance(startPort, DoublePipePortItem) and isinstance( endPort, DoublePipePortItem): command = CreateDoublePipeConnectionCommand( startPort, endPort, self) else: raise AssertionError( "Can only connect port items. Also, they have to be of the same type." ) self.parent().undoStack.push(command) def sceneMouseMoveEvent(self, event): """ This function is for dragging and connecting one port to another. When dragging, the fromPort will remain enlarged and black in color and when the toPort is hovered over, it will be enlarged and turn red. A port's details will also be displayed at the widget when they are hovered over. """ fromPort = self._currentlyDraggedConnectionFromPort if not fromPort: return fromX = fromPort.scenePos().x() fromY = fromPort.scenePos().y() toX = event.scenePos().x() toY = event.scenePos().y() self.connLine.setLine(fromX, fromY, toX, toY) self.connLineItem.setLine(self.connLine) self.connLineItem.setVisible(True) hitPortItem = self._getHitPortItemOrNone(event) if not hitPortItem: return mousePosition = event.scenePos() portItemX = hitPortItem.scenePos().x() portItemY = hitPortItem.scenePos().y() distance = _math.sqrt((mousePosition.x() - portItemX)**2 + (mousePosition.y() - portItemY)**2) if distance <= 3.5: hitPortItem.enlargePortSize() hitPortItem.innerCircle.setBrush(hitPortItem.ashColorR) self.listV.clear() hitPortItem.debugprint() else: hitPortItem.resetPortSize() hitPortItem.innerCircle.setBrush(hitPortItem.visibleColor) self.listV.clear() fromPort.debugprint() fromPort.enlargePortSize() fromPort.innerCircle.setBrush(hitPortItem.visibleColor) def _getHitPortItemOrNone(self, event: QEvent) -> _tp.Optional[PortItemBase]: fromPort = self._currentlyDraggedConnectionFromPort mousePosition = event.scenePos() relevantPortItems = self._getRelevantHitPortItems( mousePosition, fromPort) if not relevantPortItems: return None numberOfHitPortsItems = len(relevantPortItems) if numberOfHitPortsItems > 1: raise NotImplementedError( "Can't deal with overlapping port items.") hitPortItem = relevantPortItems[0] return hitPortItem def sceneMouseReleaseEvent(self, event): if not self._currentlyDraggedConnectionFromPort: return fromPort = self._currentlyDraggedConnectionFromPort self._currentlyDraggedConnectionFromPort = None self.connLineItem.setVisible(False) mousePosition = event.scenePos() relevantPortItems = self._getRelevantHitPortItems( mousePosition, fromPort) numberOfHitPortsItems = len(relevantPortItems) if numberOfHitPortsItems > 1: raise NotImplementedError( "Can't deal with overlapping port items.") if numberOfHitPortsItems == 1: toPort = relevantPortItems[0] if toPort != fromPort: self._createConnection(fromPort, toPort) def _getRelevantHitPortItems( self, mousePosition: QPointF, fromPort: PortItemBase) -> _tp.Sequence[PortItemBase]: hitItems = self.diagramScene.items(mousePosition) relevantPortItems = [ i for i in hitItems if isinstance(i, PortItemBase) and type(i) == type(fromPort) and not i.connectionList ] return relevantPortItems def cleanUpConnections(self): for c in self.connectionList: c.niceConn() def exportHydraulics(self, exportTo=_tp.Literal["ddck", "mfs"]): assert exportTo in ["ddck", "mfs"] if not self._isHydraulicConnected(): messageBox = QMessageBox() messageBox.setWindowTitle("Hydraulic not connected") messageBox.setText( "You need to connect all port items before you can export the hydraulics." ) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.exec() return self.logger.info( "------------------------> START OF EXPORT <------------------------" ) self.sortTrnsysObj() fullExportText = "" ddckFolder = os.path.join(self.projectFolder, "ddck") if exportTo == "mfs": mfsFileName = self.diagramName.split(".")[0] + "_mfs.dck" exportPath = os.path.join(self.projectFolder, mfsFileName) elif exportTo == "ddck": exportPath = os.path.join(ddckFolder, "hydraulic\\hydraulic.ddck") if self._doesFileExistAndDontOverwrite(exportPath): return None self.logger.info("Printing the TRNSYS file...") if exportTo == "mfs": header = open(os.path.join(ddckFolder, "generic\\head.ddck"), "r") headerLines = header.readlines() for line in headerLines: if line[:4] == "STOP": fullExportText += "STOP = 1 \n" else: fullExportText += line header.close() elif exportTo == "ddck": fullExportText += "*************************************\n" fullExportText += "** BEGIN hydraulic.ddck\n" fullExportText += "*************************************\n\n" fullExportText += "*************************************\n" fullExportText += "** Outputs to energy balance in kWh\n" fullExportText += ( "** Following this naming standard : qSysIn_name, qSysOut_name, elSysIn_name, elSysOut_name\n" ) fullExportText += "*************************************\n" fullExportText += "EQUATIONS 1\n" fullExportText += "qSysOut_PipeLoss = PipeLossTot\n" simulationUnit = 450 simulationType = 935 descConnLength = 20 exporter = self._createExporter() blackBoxProblem, blackBoxText = exporter.exportBlackBox( exportTo=exportTo) if blackBoxProblem: return None fullExportText += blackBoxText if exportTo == "mfs": fullExportText += exporter.exportMassFlows() fullExportText += exporter.exportPumpOutlets() fullExportText += exporter.exportDivSetting(simulationUnit - 10) fullExportText += exporter.exportDoublePipeParameters( exportTo=exportTo) fullExportText += exporter.exportParametersFlowSolver( simulationUnit, simulationType, descConnLength) fullExportText += exporter.exportInputsFlowSolver() fullExportText += exporter.exportOutputsFlowSolver(simulationUnit) fullExportText += exporter.exportFluids() + "\n" fullExportText += exporter.exportHydraulicLoops() + "\n" fullExportText += exporter.exportPipeAndTeeTypesForTemp( simulationUnit + 1) # DC-ERROR fullExportText += exporter.exportPrintPipeLosses() fullExportText += exporter.exportMassFlowPrinter( self.printerUnitnr, 15) fullExportText += exporter.exportTempPrinter(self.printerUnitnr + 1, 15) if exportTo == "mfs": fullExportText += "CONSTANTS 1\nTRoomStore=1\n" fullExportText += "ENDS" self.logger.info( "------------------------> END OF EXPORT <------------------------" ) if exportTo == "mfs": f = open(exportPath, "w") f.truncate(0) f.write(fullExportText) f.close() elif exportTo == "ddck": if fullExportText[:1] == "\n": fullExportText = fullExportText[1:] hydraulicFolder = os.path.split(exportPath)[0] if not (os.path.isdir(hydraulicFolder)): os.makedirs(hydraulicFolder) f = open(exportPath, "w") f.truncate(0) f.write(fullExportText) f.close() try: lines = _du.loadDeck(exportPath, eraseBeginComment=True, eliminateComments=True) _du.checkEquationsAndConstants(lines, exportPath) except Exception as error: errorMessage = f"An error occurred while exporting the system hydraulics: {error}" _errs.showErrorMessageBox(errorMessage) return None return exportPath def _createExporter(self) -> Export: massFlowContributors = self._getMassFlowContributors() exporter = Export(massFlowContributors, self) return exporter def _getMassFlowContributors( self) -> _tp.Sequence[_mfs.MassFlowNetworkContributorMixin]: massFlowContributors = [ o for o in self.trnsysObj if isinstance(o, _mfs.MassFlowNetworkContributorMixin) ] return massFlowContributors def _isHydraulicConnected(self) -> bool: for obj in self.trnsysObj: if not isinstance(obj, _mfs.MassFlowNetworkContributorMixin): continue internalPiping = obj.getInternalPiping() for portItem in internalPiping.modelPortItemsToGraphicalPortItem.values( ): if not portItem.connectionList: return False return True def _doesFileExistAndDontOverwrite(self, folderPath): if not _pl.Path(folderPath).exists(): return False qmb = QMessageBox(self) qmb.setText( f"Warning: {folderPath} already exists. Do you want to overwrite it or cancel?" ) qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) qmb.setDefaultButton(QMessageBox.Cancel) ret = qmb.exec() if ret == QMessageBox.Cancel: self.canceled = True self.logger.info("Canceling") return True self.canceled = False self.logger.info("Overwriting") return False def exportHydraulicControl(self): self.logger.info( "------------------------> START OF EXPORT <------------------------" ) self.sortTrnsysObj() fullExportText = "" ddckFolder = os.path.join(self.projectFolder, "ddck") hydCtrlPath = os.path.join(ddckFolder, "control\\hydraulic_control.ddck") if _pl.Path(hydCtrlPath).exists(): qmb = QMessageBox(self) qmb.setText( "Warning: " + "The file hydraulic_control.ddck already exists in the control folder. Do you want to overwrite it or cancel?" ) qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) qmb.setDefaultButton(QMessageBox.Cancel) ret = qmb.exec() if ret == QMessageBox.Save: self.canceled = False self.logger.info("Overwriting") else: self.canceled = True self.logger.info("Canceling") return fullExportText += "*************************************\n" fullExportText += "**BEGIN hydraulic_control.ddck\n" fullExportText += "*************************************\n" simulationUnit = 450 exporter = self._createExporter() fullExportText += exporter.exportPumpOutlets() fullExportText += exporter.exportMassFlows() fullExportText += exporter.exportDivSetting(simulationUnit - 10) self.logger.info( "------------------------> END OF EXPORT <------------------------" ) if fullExportText[:1] == "\n": fullExportText = fullExportText[1:] controlFolder = os.path.split(hydCtrlPath)[0] if not (os.path.isdir(controlFolder)): os.makedirs(controlFolder) f = open(str(hydCtrlPath), "w") f.truncate(0) f.write(fullExportText) f.close() return hydCtrlPath def sortTrnsysObj(self): res = self.trnsysObj.sort(key=self.sortId) for s in self.trnsysObj: self.logger.debug("s has tr id " + str(s.trnsysId) + " has dname " + s.displayName) def sortId(self, l1): """ Sort function returning a sortable key Parameters ---------- l1 : Block/Connection Returns ------- """ return l1.trnsysId def setName(self, newName): self.diagramName = newName def delBlocks(self): """ Deletes the whole diagram Returns ------- """ self.hydraulicLoops.clear() while len(self.trnsysObj) > 0: self.logger.info("In deleting...") self.trnsysObj[0].deleteBlock() while len(self.graphicalObj) > 0: self.graphicalObj[0].deleteBlock() # Encoding / decoding def encodeDiagram(self, filename): """ Encodes the diagram to a json file. Parameters ---------- filename : str Returns ------- """ self.logger.info("filename is at encoder " + str(filename)) # if filename != "": with open(filename, "w") as jsonfile: json.dump(self, jsonfile, indent=4, sort_keys=True, cls=Encoder) def _decodeDiagram(self, filename, loadValue="load"): self.logger.info("Decoding " + filename) with open(filename, "r") as jsonfile: blocklist = json.load(jsonfile, cls=Decoder, editor=self) blockFolderNames = [] for j in blocklist["Blocks"]: for k in j: if isinstance(k, BlockItem): k.setParent(self.diagramView) k.changeSize() self.diagramScene.addItem(k) blockFolderNames.append(k.displayName) if isinstance(k, StorageTank): k.updateImage() if isinstance(k, GraphicalItem): k.setParent(self.diagramView) self.diagramScene.addItem(k) if isinstance(k, dict): if "__idDct__" in k: # here we don't set the ids because the copyGroup would need access to idGen self.logger.debug( "Found the id dict while loading, not setting the ids" ) self.idGen.setID(k["GlobalId"]) self.idGen.setTrnsysID(k["trnsysID"]) self.idGen.setConnID(k["globalConnID"]) if "__nameDct__" in k: self.logger.debug("Found the name dict while loading") if loadValue == "load": self.diagramName = k["DiagramName"] blockFolderNames.append("generic") blockFolderNames.append("hydraulic") blockFolderNames.append("weather") blockFolderNames.append("control") ddckFolder = os.path.join(self.projectFolder, "ddck") ddckFolders = os.listdir(ddckFolder) additionalFolders = [] for folder in ddckFolders: if folder not in blockFolderNames and "StorageTank" not in folder: additionalFolders.append(folder) if len(additionalFolders) > 0: warnBox = QMessageBox() warnBox.setWindowTitle("Additional ddck-folders") if len(additionalFolders) == 1: text = "The following ddck-folder does not have a corresponding component in the diagram:" else: text = "The following ddck-folders do not have a corresponding component in the diagram:" for folder in additionalFolders: text += "\n\t" + folder warnBox.setText(text) warnBox.setStandardButtons(QMessageBox.Ok) warnBox.setDefaultButton(QMessageBox.Ok) warnBox.exec() for t in self.trnsysObj: t.assignIDsToUninitializedValuesAfterJsonFormatMigration( self.idGen) self.logger.debug("Tr obj is" + str(t) + " " + str(t.trnsysId)) if hasattr(t, "isTempering"): self.logger.debug("tv has " + str(t.isTempering)) self._decodeHydraulicLoops(blocklist) def _decodeHydraulicLoops(self, blocklist): singlePipeConnections = [ c for c in self.connectionList if isinstance(c, SinglePipeConnection) ] if "hydraulicLoops" not in blocklist: hydraulicLoops = _hlmig.createLoops(singlePipeConnections, self.fluids.WATER) else: serializedHydraulicLoops = blocklist["hydraulicLoops"] hydraulicLoops = _hlm.HydraulicLoops.createFromJson( serializedHydraulicLoops, singlePipeConnections, self.fluids) self.hydraulicLoops = hydraulicLoops def exportSvg(self): """ For exporting a svg file (text is still too large) Returns ------- """ generator = QSvgGenerator() generator.setResolution(300) generator.setSize( QSize(self.diagramScene.width(), self.diagramScene.height())) # generator.setViewBox(QRect(0, 0, 800, 800)) generator.setViewBox(self.diagramScene.sceneRect()) generator.setFileName("VectorGraphicsExport.svg") painter = QPainter() painter.begin(generator) painter.setRenderHint(QPainter.Antialiasing) self.diagramScene.render(painter) painter.end() # Saving related def save(self, showWarning=True): """ If saveas has not been used, diagram will be saved in "/diagrams" If saveas has been used, diagram will be saved in self.saveAsPath Returns ------- """ self.diagramName = os.path.split(self.projectFolder)[-1] + ".json" diagramPath = os.path.join(self.projectFolder, self.diagramName) if os.path.isfile(diagramPath) and showWarning: qmb = QMessageBox(self) qmb.setText( "Warning: " + "This diagram name exists already. Do you want to overwrite or cancel?" ) qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) qmb.setDefaultButton(QMessageBox.Cancel) ret = qmb.exec() if ret != QMessageBox.Save: self.logger.info("Canceling") return self.logger.info("Overwriting") self.encodeDiagram(diagramPath) self.encodeDiagram(diagramPath) msgb = QMessageBox(self) msgb.setText("Saved diagram at " + diagramPath) msgb.exec() def saveToProject(self): projectPath = self.projectPath def renameDiagram(self, newName): """ Parameters ---------- newName Returns ------- """ if self.saveAsPath.name != "": # print("Path name is " + self.saveAsPath.name) if newName + ".json" in self.saveAsPath.glob("*"): QMessageBox( self, "Warning", "This diagram name exists already in the directory." " Please rename this diagram") else: self.saveAsPath = _pl.Path( self.saveAsPath.stem[0:self.saveAsPath.name. index(self.diagramName)] + newName) self.diagramName = newName self.parent().currentFile = newName # fromPath = self.projectFolder # destPath = os.path.dirname(__file__) # destPath = os.path.join(destPath, 'default') # destPath = os.path.join(destPath, newName) # os.rename(fromPath, destPath) # print("Path is now: " + str(self.saveAsPath)) # print("Diagram name is: " + self.diagramName) def saveAtClose(self): self.logger.info("saveaspath is " + str(self.saveAsPath)) # closeDialog = closeDlg() # if closeDialog.closeBool: filepath = _pl.Path( _pl.Path(__file__).resolve().parent.joinpath("recent")) self.encodeDiagram(str(filepath.joinpath(self.diagramName + ".json"))) # Mode related def setAlignMode(self, b): self.alignMode = True def setEditorMode(self, b): self.editorMode = b def setMoveDirectPorts(self, b): """ Sets the bool moveDirectPorts. When mouse released in diagramScene, moveDirectPorts is set to False again Parameters ---------- b : bool Returns ------- """ self.moveDirectPorts = b def setSnapGrid(self, b): self.snapGrid = b def setSnapSize(self, s): self.snapSize = s def setConnLabelVis(self, isVisible: bool) -> None: for c in self.trnsysObj: if isinstance(c, ConnectionBase): c.setLabelVisible(isVisible) if isinstance(c, BlockItem): c.label.setVisible(isVisible) if isinstance(c, TVentil): c.posLabel.setVisible(isVisible) def updateConnGrads(self): for t in self.trnsysObj: if isinstance(t, ConnectionBase): t.updateSegGrads() # Dialog calls def showBlockDlg(self, bl): c = BlockDlg(bl, self) def showDoublePipeBlockDlg(self, bl): c = DoublePipeBlockDlg(bl, self) def showPumpDlg(self, bl): c = PumpDlg(bl, self) def showDiagramDlg(self): c = diagramDlg(self) def showGenericPortPairDlg(self, bl): c = GenericPortPairDlg(bl, self) def showHxDlg(self, hx): c = hxDlg(hx, self) def showSegmentDlg(self, seg): c = segmentDlg(seg, self) def showTVentilDlg(self, bl): c = TVentilDlg(bl, self) def showConfigStorageDlg(self, bl): c = ConfigureStorageDialog(bl, self) def getConnection(self, n): return self.connectionList[int(n)] # Unused def create_icon(self, map_icon): map_icon.fill() painter = QPainter(map_icon) painter.fillRect(10, 10, 40, 40, QColor(88, 233, 252)) # painter.setBrush(Qt.red) painter.setBrush(QColor(252, 136, 98)) painter.drawEllipse(36, 2, 15, 15) painter.setBrush(Qt.yellow) painter.drawEllipse(20, 20, 20, 20) painter.end() def setTrnsysIdBack(self): self.idGen.trnsysID = max(t.trnsysId for t in self.trnsysObj) def findStorageCorrespPorts1(self, portList): """ This function gets the ports on the other side of pipes connected to a port of the StorageTank. Unused Parameters ---------- portList : :obj:`List` of :obj:`PortItems` Returns ------- """ res = [] # print("Finding c ports") for p in portList: if len(p.connectionList) > 0: # check if not >1 needed # connectionList[0] is the hidden connection created when the portPair is i = 0 # while type(p.connectionList[i].fromPort.parent) is StorageTank and type(p.connectionList[i].toPort.parent) is StorageTank: while (p.connectionList[i].fromPort.parent) == ( p.connectionList[i].toPort.parent): i += 1 if len(p.connectionList) >= i + 1: if p.connectionList[i].fromPort is p: res.append(p.connectionList[i].toPort) elif p.connectionList[i].toPort is p: res.append(p.connectionList[i].fromPort) else: self.logger.debug("Port is not fromPort nor toPort") # [print(p.parent.displayName) for p in res] return res def printPDF(self): """ --------------------------------------------- Export diagram as pdf onto specified folder fn = user input directory --------------------------------------------- """ fn, _ = QFileDialog.getSaveFileName(self, "Export PDF", None, "PDF files (.pdf);;All Files()") if fn != "": if QFileInfo(fn).suffix() == "": fn += ".pdf" printer = QPrinter(QPrinter.HighResolution) printer.setOrientation(QPrinter.Landscape) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(fn) painter = QPainter(printer) self.diagramScene.render(painter) painter.end() self.logger.info("File exported to %s" % fn) def openProject(self): self.projectPath = str( QFileDialog.getExistingDirectory(self, "Select Project Path")) if self.projectPath != "": test = self.parent() self.parent().newDia() self.PPL.setText(self.projectPath) loadPath = os.path.join(self.projectPath, "ddck") self.createConfigBrowser(self.projectPath) self.copyGenericFolder(self.projectPath) self.createHydraulicDir(self.projectPath) self.createWeatherAndControlDirs(self.projectPath) self.createDdckTree(loadPath) # todo : open diagram # todo : add files into list def createDdckTree(self, loadPath): treeToRemove = self.findChild(QTreeView, "ddck") try: # treeToRemove.hide() treeToRemove.deleteLater() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") if self.projectPath == "": loadPath = os.path.join(loadPath, "ddck") if not os.path.exists(loadPath): os.makedirs(loadPath) self.model = MyQFileSystemModel() self.model.setRootPath(loadPath) self.model.setName("ddck") self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(loadPath)) self.tree.setObjectName("ddck") self.tree.setMinimumHeight(600) self.tree.setSortingEnabled(True) self.splitter.insertWidget(0, self.tree) def createConfigBrowser(self, loadPath): self.layoutToRemove = self.findChild(QHBoxLayout, "Config_Layout") try: # treeToRemove.hide() self.layoutToRemove.deleteLater() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") runConfigData = self._getPackageResourceData("templates/run.config") runConfigPath = _pl.Path(loadPath) / "run.config" runConfigPath.write_bytes(runConfigData) self.HBox = QHBoxLayout() self.refreshButton = QPushButton(self) self.refreshButton.setIcon(_img.ROTATE_TO_RIGHT_PNG.icon()) self.refreshButton.clicked.connect(self.refreshConfig) self.model = MyQFileSystemModel() self.model.setRootPath(loadPath) self.model.setName("Config File") self.model.setFilter(QDir.Files) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(loadPath)) self.tree.setObjectName("config") self.tree.setFixedHeight(60) self.tree.setSortingEnabled(False) self.HBox.addWidget(self.refreshButton) self.HBox.addWidget(self.tree) self.HBox.setObjectName("Config_Layout") self.fileBrowserLayout.addLayout(self.HBox) def createProjectFolder(self): if not os.path.exists(self.projectFolder): os.makedirs(self.projectFolder) def refreshConfig(self): # configPath = os.path.dirname(__file__) # configPath = os.path.join(configPath, 'project') # configPath = os.path.join(configPath, self.date_time) # emptyConfig = os.path.join(configPath, 'run.config') if self.projectPath == "": localPath = self.projectFolder else: localPath = self.projectPath self.configToEdit = os.path.join(localPath, "run.config") os.remove(self.configToEdit) shutil.copy(self.emptyConfig, localPath) self.configToEdit = os.path.join(localPath, "run.config") localDdckPath = os.path.join(localPath, "ddck") with open(self.configToEdit, "r") as file: lines = file.readlines() localPathStr = "string LOCAL$ %s" % str(localDdckPath) # localPathStr.replace('/', '\\') lines[21] = localPathStr + "\n" with open(self.configToEdit, "w") as file: file.writelines(lines) # print(localPathStr) self.userInputList() def userInputList(self): self.logger.debug(self.fileList) dia = FileOrderingDialog(self.fileList, self) def copyGenericFolder(self, loadPath): genericFolderPath = _pl.Path(loadPath) / "ddck" / "generic" if not genericFolderPath.exists(): self.logger.info("Creating %s", genericFolderPath) genericFolderPath.mkdir() headData = self._getPackageResourceData("templates/generic/head.ddck") self.logger.info("Copying head.ddck") (genericFolderPath / "head.ddck").write_bytes(headData) endData = self._getPackageResourceData("templates/generic/end.ddck") self.logger.info("Copying end.ddck") (genericFolderPath / "end.ddck").write_bytes(endData) @staticmethod def _getPackageResourceData(resourcePath): data = _pu.get_data(_tgui.__name__, resourcePath) assert data, f"{resourcePath} package resource not found" return data def createHydraulicDir(self, projectPath): self.hydraulicFolder = os.path.join(projectPath, "ddck") self.hydraulicFolder = os.path.join(self.hydraulicFolder, "hydraulic") if not os.path.exists(self.hydraulicFolder): self.logger.info("Creating " + self.hydraulicFolder) os.makedirs(self.hydraulicFolder) def createWeatherAndControlDirs(self, projectPath): ddckFolder = os.path.join(projectPath, "ddck") weatherFolder = os.path.join(ddckFolder, "weather") controlFolder = os.path.join(ddckFolder, "control") if not os.path.exists(weatherFolder): self.logger.info("Creating " + weatherFolder) os.makedirs(weatherFolder) if not os.path.exists(controlFolder): self.logger.info("Creating " + controlFolder) os.makedirs(controlFolder) def editHydraulicLoop(self, singlePipeConnection: SinglePipeConnection): assert isinstance(singlePipeConnection.fromPort, SinglePipePortItem) hydraulicLoop = self.hydraulicLoops.getLoopForExistingConnection( singlePipeConnection) _hledit.edit(hydraulicLoop, self.hydraulicLoops, self.fluids)
class WTap(BlockItem, MassFlowNetworkContributorMixin): def __init__(self, trnsysType, parent, **kwargs): super(WTap, self).__init__(trnsysType, parent, **kwargs) self.w = 40 self.h = 40 self.inputs.append(_cspi.createSinglePipePortItem("i", 0, self)) self.loadedFiles = [] self.changeSize() self.addTree() def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.W_TAP_SVG def changeSize(self): w = self.w h = self.h """ Resize block function """ delta = 20 deltaHF = 0.45 # 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.origInputsPos = [[0, delta]] self.inputs[0].setPos(self.origInputsPos[0][0], self.origInputsPos[0][1]) self.updateFlipStateH(self.flippedH) self.updateFlipStateV(self.flippedV) self.inputs[0].side = (self.rotationN + 2 * self.flippedH) % 4 return w, h def exportPumpOutlets(self): resStr = "T" + self.displayName + " = " + "T" + self.inputs[ 0].connectionList[0].displayName + "\n" equationNr = 1 return resStr, equationNr def getInternalPiping(self) -> InternalPiping: portItem = _mfn.PortItem("WTap Input", _mfn.PortItemType.INPUT) sink = _mfn.Sink(self.displayName, self.trnsysId, portItem) return InternalPiping([sink], {portItem: self.inputs[0]}) def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ self.logger.debug(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": # self.path = os.path.dirname(__file__) # self.path = os.path.join(self.path, 'default') self.path = self.parent.parent().projectFolder # now = datetime.now() # self.fileName = now.strftime("%Y%m%d%H%M%S") # self.path = os.path.join(self.path, self.fileName) else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # self.logger.debug("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) # self.logger.debug("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild( QTreeView, self.displayName + "Tree") shutil.rmtree(self.path) self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) self.logger.debug(os.path.dirname(self.path)) # destPath = str(os.path.dirname(self.path))+'\\WTap_'+self.displayName destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)
class Control(BlockItem): def __init__(self, trnsysType, parent, **kwargs): super(Control, self).__init__(trnsysType, parent, **kwargs) factor = 0.667 # 0.63 for png self.w = 100 self.h = 100 self.loadedFiles = [] self.addTree() def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ print(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": # self.path = os.path.dirname(__file__) # self.path = os.path.join(self.path, 'default') self.path = self.parent.parent().projectFolder # now = datetime.now() # self.fileName = now.strftime("%Y%m%d%H%M%S") # self.path = os.path.join(self.path, self.fileName) else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ print("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # print("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) print("deleting block " + str(self) + self.displayName) # print("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild( QTreeView, self.displayName + "Tree") shutil.rmtree(self.path) self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: print("Widget doesnt exist!") else: print("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) print(os.path.dirname(self.path)) destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath print(self.path)
class GenericBlock(BlockItem, MassFlowNetworkContributorMixin): def __init__(self, trnsysType, parent, **kwargs): super(GenericBlock, self).__init__(trnsysType, parent, **kwargs) self.inputs.append(_cspi.createSinglePipePortItem("i", 2, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) self.loadedFiles = [] self.childIds = [] self.childIds.append(self.trnsysId) self._imageAccessor = _img.GENERIC_BLOCK_PNG # Disallow adding port pairs later, because the trnsysIDs of the generated port pairs have to be # consecutive to be correctly printed out in the export self.isSet = True self.changeSize() self.addTree() def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.GENERIC_BLOCK_PNG def changeSize(self): w = self.w h = self.h delta = 4 """ Resize block function """ # 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.outputs[0].setPos(2 * delta + w, h - 2 * delta) self.inputs[0].setPos(2 * delta + w, 2 * delta) return w, h def getPairNb(self, side): res = 0 for i in self.inputs: if i.side == side: res += 1 self.logger.debug("there are " + str(res) + " pairs on the side " + str(side)) return res def addPortDlg(self): self.parent.parent().showGenericPortPairDlg(self) def addPort(self, io, relH): self.logger.debug(io) self.logger.debug(relH) def setImage(self): pixmap = self._getPixmap() self.setPixmap(pixmap) def changeImage(self): name = str(self.pickImage().resolve()) if name[-3:] == "png" or name[-3:] == "svg": self.setImageSource(name) self.setImage() else: self.logger.debug("No image picked, name is " + name) def setImageSource(self, name): self._imageAccessor = _img.ImageAccessor.createForFile(_pl.Path(name)) def pickImage(self): return _pl.Path( QFileDialog.getOpenFileName(self.parent.parent(), filter="*.png *.svg")[0]) 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.deleteBlock) c3 = menu.addAction("Set image") c3.triggered.connect(self.changeImage) if not self.isSet: c4 = menu.addAction("Add port") c4.triggered.connect(self.addPortDlg) menu.exec_(event.screenPos()) def encode(self): if self.isVisible(): self.logger.debug("Encoding a Generic Block") portListInputs = [] portListOutputs = [] for p in self.inputs: portListInputs.append(p.id) for p in self.outputs: portListOutputs.append(p.id) dct = {} dct[".__BlockDict__"] = True dct["BlockName"] = self.name dct["BlockDisplayName"] = self.displayName dct["BlockPosition"] = (float(self.pos().x()), float(self.pos().y())) dct["ID"] = self.id dct["trnsysID"] = self.trnsysId dct["PortsIDIn"] = portListInputs dct["PortsIDOut"] = portListOutputs dct["FlippedH"] = self.flippedH dct["FlippedV"] = self.flippedV dct["RotationN"] = self.rotationN dct["Imagesource"] = self._imageAccessor.getResourcePath() dct["PortPairsNb"] = [self.getPairNb(i) for i in range(4)] dictName = "Block-" return dictName, dct def decode(self, i, resBlockList): assert len(self.inputs) == len(self.outputs) numberOfPortPairs = len(self.inputs) for portPairIndex in range(numberOfPortPairs): self.removePortPair(portPairIndex) numberOfPortPairsBySide = i["PortPairsNb"] for side in range(3): numberOfPortPairsToAdd = numberOfPortPairsBySide[side] for _ in range(numberOfPortPairsToAdd): self.addPortPair(side) super(GenericBlock, self).decode(i, resBlockList) self._imageAccessor = _img.ImageAccessor.createFromResourcePath( i["Imagesource"]) self.setImage() def decodePaste(self, i, offset_x, offset_y, resConnList, resBlockList, **kwargs): correcter = 0 for j in range(4): if j == 2: correcter = -1 for k in range(i["PortPairsNb"][j] + correcter): self.addPortPair(j) super(GenericBlock, self).decodePaste(i, offset_x, offset_y, resConnList, resBlockList) self._imageAccessor = _img.ImageAccessor.createFromResourcePath( i["Imagesource"]) self.setImage() def addPortPair(self, side): h = self.h w = self.w delta = 4 self.logger.debug("side is " + str(side)) self.inputs.append(_cspi.createSinglePipePortItem("i", side, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", side, self)) # Allocate id self.childIds.append(self.parent.parent().idGen.getTrnsysID()) portNb = [0, 0, 0, 0] for i in self.inputs: if i.side == 0: distBetweenPorts = (self.h - 4 * delta) / (2 * self.getPairNb(0) - 1) self.logger.debug("distance betw ports " + str(distBetweenPorts)) i.setPos(-2 * delta, 2 * delta + distBetweenPorts * portNb[0]) portNb[0] += 1 self.outputs[self.inputs.index(i)].setPos( -2 * delta, 2 * delta + distBetweenPorts * portNb[0]) portNb[0] += 1 elif i.side == 1: distBetweenPorts = (self.w - 4 * delta) / (2 * self.getPairNb(1) - 1) i.setPos(2 * delta + distBetweenPorts * portNb[1], -2 * delta) portNb[1] += 1 self.outputs[self.inputs.index(i)].setPos( 2 * delta + distBetweenPorts * portNb[1], -2 * delta) portNb[1] += 1 elif i.side == 2: self.logger.debug("side == 2") distBetweenPorts = (self.h - 4 * delta) / (2 * self.getPairNb(2) - 1) self.logger.debug("side 2 dist betw ports is " + str(distBetweenPorts)) i.setPos(2 * delta + w, 2 * delta + distBetweenPorts * portNb[2]) self.logger.debug(2 * delta + distBetweenPorts * portNb[2]) portNb[2] += 1 self.outputs[self.inputs.index(i)].setPos( 2 * delta + w, 2 * delta + distBetweenPorts * portNb[2]) self.logger.debug(2 * delta + distBetweenPorts * portNb[2]) portNb[2] += 1 else: distBetweenPorts = (self.w - 4 * delta) / (2 * self.getPairNb(3) - 1) self.logger.debug("distance betw ports " + str(distBetweenPorts)) i.setPos(2 * delta + distBetweenPorts * portNb[3], 2 * delta + h) portNb[3] += 1 self.outputs[self.inputs.index(i)].setPos( 2 * delta + distBetweenPorts * portNb[3], 2 * delta + h) portNb[3] += 1 def removePortPairOnSide(self, side): for i in self.inputs: if i.side == side: self.removePortPair(self.inputs.index(i)) return def removePortPair(self, n): self.inputs.remove(self.inputs[n]) self.outputs.remove(self.outputs[n]) def updateFlipStateH(self, state): self.flippedH = bool(state) pixmap = self._getPixmap() self.setPixmap(pixmap) def updateFlipStateV(self, state): self.flippedV = bool(state) pixmap = self._getPixmap() self.setPixmap(pixmap) def getInternalPiping(self) -> InternalPiping: assert len(self.inputs) == len(self.outputs) pipes = [] portItems = {} for i, (graphicalInputPort, graphicalOutputPort) in enumerate( zip(self.inputs, self.outputs)): inputPort = _mfn.PortItem(f"Input {i+1}", _mfn.PortItemType.INPUT) outputPort = _mfn.PortItem(f"Output {i+1}", _mfn.PortItemType.OUTPUT) pipe = _mfn.Pipe(f"{self.displayName}X{i}", self.childIds[0], inputPort, outputPort) pipes.append(pipe) portItems[inputPort] = graphicalInputPort portItems[outputPort] = graphicalOutputPort return InternalPiping(pipes, portItems) def getSubBlockOffset(self, c): for i in range(len(self.inputs)): if (self.inputs[i] == c.toPort or self.inputs[i] == c.fromPort or self.outputs[i] == c.toPort or self.outputs[i] == c.fromPort): return i def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ self.logger.debug(self.parent.parent()) pathName = self.displayName if self.parent.parent().projectPath == "": self.path = self.parent.parent().projectFolder else: self.path = self.parent.parent().projectPath self.path = os.path.join(self.path, "ddck") self.path = os.path.join(self.path, pathName) if not os.path.exists(self.path): os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName("%sTree" % self.displayName) for i in range(1, self.model.columnCount() - 1): self.tree.hideColumn(i) self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild( QTreeView, self.displayName + "Tree") shutil.rmtree(self.path) self.deleteLoadedFile() try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName("%sTree" % self.displayName) self.logger.debug(os.path.dirname(self.path)) destPath = os.path.join(os.path.split(self.path)[0], self.displayName) if os.path.exists(self.path): os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)
class StorageTank(BlockItem, MassFlowNetworkContributorMixin): # pylint: disable=too-many-instance-attributes,too-many-public-methods HEAT_EXCHANGER_WIDTH = 40 def __init__(self, trnsysType, parent, **kwargs): super().__init__(trnsysType, parent, **kwargs) self.parent = parent self._idGenerator: _id.IdGenerator = self.parent.parent().idGen self.dckFilePath = "" self.directPortPairs: _tp.List[DirectPortPair] = [] self.heatExchangers: _tp.List[HeatExchanger] = [] self.blackBoxEquations = [] self.nTes = self.parent.parent().idGen.getStoragenTes() self.storageType = self.parent.parent().idGen.getStorageType() self.changeSize() self.path = None self.addTree() @property def leftDirectPortPairsPortItems(self): return self._getDirectPortPairPortItems(_sd.Side.LEFT) @property def rightDirectPortPairsPortItems(self): return self._getDirectPortPairPortItems(_sd.Side.RIGHT) def _getDirectPortPairPortItems(self, side: _sd.Side): return [ p for dpp in self.directPortPairs if dpp.side == side for p in [dpp.fromPort, dpp.toPort] ] def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: return _img.STORAGE_TANK_SVG # Setter functions def setParent(self, p): self.logger.debug("Setting parent of Storage Tank (and its hx)") self.parent = p if self not in self.parent.parent().trnsysObj: self.parent.parent().trnsysObj.append(self) for heatExchanger in self.heatExchangers: heatExchanger.parent = self def addDirectPortPair( # pylint: disable=too-many-arguments self, trnsysId: int, side: _sd.Side, relativeInputHeight: float, relativeOutputHeight: float, storageTankHeight: float, portIds: _tp.Optional[PortIds] = None, ): inputPort = self._createPort("i", relativeInputHeight, storageTankHeight, side) outputPort = self._createPort("o", relativeOutputHeight, storageTankHeight, side) randomInt = int(_rnd.uniform(20, 200)) randomColor = QColor(randomInt, randomInt, randomInt) self._setPortColor(inputPort, randomColor) self._setPortColor(outputPort, randomColor) if portIds: inputPort.id = portIds.inputId outputPort.id = portIds.outputId directPortPair = DirectPortPair( trnsysId, inputPort, outputPort, relativeInputHeight, relativeOutputHeight, side ) self.directPortPairs.append(directPortPair) self.inputs.append(directPortPair.fromPort) self.outputs.append(directPortPair.toPort) def _createPort( self, name: str, relativeHeight: float, storageTankHeight: float, side: _sd.Side ) -> SinglePipePortItem: sideNr = side.toSideNr() portItem = _cspi.createSinglePipePortItem(name, sideNr, self) portItem.setZValue(100) xPos = 0 if side == _sd.Side.LEFT else self.w yPos = storageTankHeight - relativeHeight * storageTankHeight portItem.setPos(xPos, yPos) portItem.side = sideNr return portItem @staticmethod def _setPortColor(portItem: SinglePipePortItem, color: QColor) -> None: portItem.innerCircle.setBrush(color) portItem.visibleColor = color def addHeatExchanger(self, name, trnsysId, side, relativeInputHeight, relativeOutputHeight): heatExchanger = HeatExchanger( trnsysId=trnsysId, sideNr=side.toSideNr(), width=self.HEAT_EXCHANGER_WIDTH, relativeInputHeight=relativeInputHeight, relativeOutputHeight=relativeOutputHeight, storageTankWidth=self.w, storageTankHeight=self.h, parent=self, name=name, ) return heatExchanger def updateImage(self): super().updateImage() self.label.setPos(self.label.pos().x(), self.h) def updatePortItemPositions(self, deltaH, deltaW): for portItem in self.inputs + self.outputs: oldRelativeHeight = portItem.pos().y() / self.h if portItem.side == 0: portItem.setPos(portItem.pos().x(), oldRelativeHeight * (self.h + deltaH)) else: portItem.setPos(portItem.pos().x() + deltaW, oldRelativeHeight * (self.h + deltaH)) def updateHeatExchangersAfterTankSizeChange(self): for heatExchanger in self.heatExchangers: heatExchanger.setTankSize(self.w, self.h) def encode(self): if not self.isVisible(): raise RuntimeError("Cannot encode an invisible storage tank.") heatExchangerModels = self._getHeatExchangerModelsForEncode() portPairModels = self._getDirectPortPairModelsForEncode() position = float(self.pos().x()), float(self.pos().y()) storageTankModel = _model.StorageTank( self.flippedH, self.flippedV, self.name, self.displayName, self.id, self.trnsysId, self.h, position, heatExchangerModels, portPairModels, ) dictName = "Block-" return dictName, storageTankModel.to_dict() def _getDirectPortPairModelsForEncode(self): portPairModels = [] for directPort in self.directPortPairs: side = _sd.Side.createFromSideNr(directPort.fromPort.side) inputPortModel = _model.Port(directPort.fromPort.id, directPort.relativeInputHeight) outputPortModel = _model.Port(directPort.toPort.id, directPort.relativeOutputHeight) portPairModel = _model.PortPair(side, directPort.trnsysId, inputPortModel, outputPortModel) directPortPairModel = _model.DirectPortPair(portPairModel) portPairModels.append(directPortPairModel) return portPairModels def _getHeatExchangerModelsForEncode(self): heatExchangerModels = [] for heatExchanger in self.heatExchangers: side = _sd.Side.createFromSideNr(heatExchanger.sSide) inputPort = _model.Port( heatExchanger.port1.id, heatExchanger.relativeInputHeight, ) outputPort = _model.Port( heatExchanger.port2.id, heatExchanger.relativeOutputHeight, ) portPair = _model.PortPair(side, heatExchanger.trnsysId, inputPort, outputPort) heatExchangerModel = _model.HeatExchanger( portPair, heatExchanger.displayName, heatExchanger.w, self.id, heatExchanger.id ) heatExchangerModels.append(heatExchangerModel) return heatExchangerModels def decode(self, i, resBlockList): offsetX = 0 offsetY = 0 self._decodeInternal(i, offsetX, offsetY, resBlockList, shallSetNamesAndIDs=True) def _decodeInternal( # pylint: disable=too-many-arguments self, i, offsetX, offsetY, resBlockList, shallSetNamesAndIDs: bool, ): self.logger.debug("Loading a Storage in Decoder") model = _model.StorageTank.from_dict(i) self.flippedH = model.isHorizontallyFlipped if shallSetNamesAndIDs: self.displayName = model.BlockDisplayName self.changeSize() self.h = model.height self.updateImage() self.setPos(model.position[0] + offsetX, model.position[1] + offsetY) if shallSetNamesAndIDs: self.trnsysId = model.trnsysId self.id = model.id for heatExchangerModel in model.heatExchangers: self._decodeHeatExchanger(heatExchangerModel, shallSetNamesAndIDs) for portPairModel in model.directPortPairs: self._decodeDirectPortPair(portPairModel) resBlockList.append(self) def _decodeDirectPortPair( self, portPairModel: _model.DirectPortPair, ) -> None: portPair = portPairModel.portPair portIds = PortIds(portPair.inputPort.id, portPair.outputPort.id) self.addDirectPortPair( portPair.trnsysId, portPair.side, portPair.inputPort.relativeHeight, portPair.outputPort.relativeHeight, storageTankHeight=self.h, portIds=portIds, ) def _decodeHeatExchanger(self, heatExchangerModel: _model.HeatExchanger, shallSetNamesAndIDs: bool): portPair = heatExchangerModel.portPair nameSuffix = "" if shallSetNamesAndIDs else "New" name = heatExchangerModel.name + nameSuffix heatExchanger = self.addHeatExchanger( name, portPair.trnsysId, portPair.side, portPair.inputPort.relativeHeight, portPair.outputPort.relativeHeight ) if shallSetNamesAndIDs: heatExchanger.setId(heatExchangerModel.id) heatExchanger.port1.id = portPair.inputPort.id heatExchanger.port2.id = portPair.outputPort.id def decodePaste( # pylint: disable=too-many-arguments self, i, offset_x, offset_y, resConnList, resBlockList, **kwargs ): self._decodeInternal(i, offset_x, offset_y, resBlockList, shallSetNamesAndIDs=False) def getTemperatureVariableName(self, portItem: SinglePipePortItem) -> str: directPortPair = self._getDirectPortPairForPortItemOrNone(portItem) if directPortPair: return self._getTemperatureVariableNameForDirectPortPairPortItem(directPortPair, portItem) heatExchanger = self._getHeatExchangerForPortItem(portItem) if heatExchanger: return self._getTemperatureVariableNameForHeatExchangerPortItem(heatExchanger) raise ValueError("Port item doesn't belong to this storage tank.") def getFlowSolverParametersId(self, portItem: SinglePipePortItem) -> int: directPortPair = self._getDirectPortPairForPortItemOrNone(portItem) if directPortPair: return directPortPair.trnsysId heatExchanger = self._getHeatExchangerForPortItem(portItem) if heatExchanger: return heatExchanger.trnsysId raise ValueError("Port item doesn't belong to this storage tank.") def assignIDsToUninitializedValuesAfterJsonFormatMigration( self, generator: _id.IdGenerator ) -> None: # type: ignore[attr-defined] for heatExchanger in self.heatExchangers: if heatExchanger.trnsysId == generator.UNINITIALIZED_ID: heatExchanger.trnsysId = generator.getTrnsysID() for directPortPair in self.directPortPairs: if directPortPair.trnsysId == generator.UNINITIALIZED_ID: directPortPair.trnsysId = generator.getTrnsysID() def _getHeatExchangerForPortItem(self, portItem: SinglePipePortItem) -> _tp.Optional[HeatExchanger]: heatExchanger = self._getSingleOrNone(hx for hx in self.heatExchangers if portItem in [hx.port1, hx.port2]) return heatExchanger def _getDirectPortPairForPortItemOrNone(self, portItem: SinglePipePortItem) -> _tp.Optional[DirectPortPair]: directPortPair = self._getSingleOrNone( dpp for dpp in self.directPortPairs if portItem in [dpp.fromPort, dpp.toPort] ) return directPortPair @staticmethod def _getSingleOrNone(iterable: _tp.Iterable[_T]) -> _tp.Optional[_T]: sequence = list(iterable) if not sequence: return None if len(sequence) > 1: raise ValueError("More than one value in iterable.") return sequence[0] def _getTemperatureVariableNameForDirectPortPairPortItem(self, directPortPair, portItem): isInputPort = directPortPair.fromPort == portItem relativeHeightInPercent = ( directPortPair.relativeInputHeightPercent if isInputPort else directPortPair.relativeOutputHeightPercent ) return f"T{self.displayName}Port{directPortPair.side.formatDdck()}{relativeHeightInPercent}" @staticmethod def _getTemperatureVariableNameForHeatExchangerPortItem(heatExchanger): return f"T{heatExchanger.displayName}" # Misc def contextMenuEvent(self, event): menu = QMenu() launchNotepadAction = menu.addAction("Launch NotePad++") launchNotepadAction.triggered.connect(self.launchNotepadFile) rotateRightIcon = _img.ROTATE_TO_RIGHT_PNG.icon() rotateRightAction = menu.addAction(rotateRightIcon, "Rotate Block clockwise") rotateRightAction.triggered.connect(self.rotateBlockCW) rotateLeftIcon = _img.ROTATE_LEFT_PNG.icon() rotateLeftIcon = menu.addAction(rotateLeftIcon, "Rotate Block counter-clockwise") rotateLeftIcon.triggered.connect(self.rotateBlockCCW) resetRotationAction = menu.addAction("Reset Rotation") resetRotationAction.triggered.connect(self.resetRotation) printRotationAction = menu.addAction("Print Rotation") printRotationAction.triggered.connect(self.printRotation) deleteBlockAction = menu.addAction("Delete this Block") deleteBlockAction.triggered.connect(self.deleteBlockCom) exportDdckAction = menu.addAction("Export ddck") exportDdckAction.triggered.connect(self.exportDck) menu.exec(event.screenPos()) def mouseDoubleClickEvent(self, event): ConfigureStorageDialog(self, self.scene().parent()) # Export related def exportBlackBox(self): equations = [] ddcxPath = _os.path.join(self.path, self.displayName) ddcxPath = ddcxPath + ".ddcx" self.exportDck() if _os.path.isfile(ddcxPath): with open(ddcxPath, "r", encoding="windows-1252") as infile: lines = infile.readlines() for line in lines: if line[0] == "T": equations.append(line.replace("\n", "")) return "success", equations self.logger.warning("No file at " + ddcxPath) return "noDdckFile", equations def getInternalPiping(self) -> InternalPiping: heatExchangerNodes, heatExchangerPortItems = self._createHeatExchangerNodes() portPairNodes, portPairsPortItems = self._createPortPairNodes() nodes = [*heatExchangerNodes, *portPairNodes] modelPortItemsToGraphicalPortItem = heatExchangerPortItems | portPairsPortItems return InternalPiping(nodes, modelPortItemsToGraphicalPortItem) def _createHeatExchangerNodes(self): heatExchangerPortItems = {} heatExchangerNodes = [] for heatExchanger in self.heatExchangers: heatExchangerPortItem1 = _mfn.PortItem("Heat Exchanger Input", _mfn.PortItemType.INPUT) heatExchangerPortItems[heatExchangerPortItem1] = heatExchanger.port1 heatExchangerPortItem2 = _mfn.PortItem("Heat Exchanger Output", _mfn.PortItemType.OUTPUT) heatExchangerPortItems[heatExchangerPortItem2] = heatExchanger.port2 name = self._getMassFlowVariableSuffixForHeatExchanger(heatExchanger) node = _mfn.Pipe(name, heatExchanger.trnsysId, heatExchangerPortItem1, heatExchangerPortItem2) heatExchangerNodes.append(node) return heatExchangerNodes, heatExchangerPortItems def _createPortPairNodes(self): portPairsPortItems = {} portPairNodes = [] for directPortPair in self.directPortPairs: portPairPortItem1 = _mfn.PortItem("Input", _mfn.PortItemType.INPUT) portPairsPortItems[portPairPortItem1] = directPortPair.fromPort portPairPortItem2 = _mfn.PortItem("Output", _mfn.PortItemType.OUTPUT) portPairsPortItems[portPairPortItem2] = directPortPair.toPort portPairName = self._getMassFlowVariableSuffixForDirectPortPair(directPortPair) node = _mfn.Pipe(portPairName, directPortPair.trnsysId, portPairPortItem1, portPairPortItem2) portPairNodes.append(node) return portPairNodes, portPairsPortItems def _getMassFlowVariableSuffixForDirectPortPair(self, directPortPair: DirectPortPair): return ( f"{self.displayName}Dp{'L' if directPortPair.side.isLeft else 'R'}" f"{directPortPair.relativeInputHeightPercent}-{directPortPair.relativeOutputHeightPercent}" ) @staticmethod def _getMassFlowVariableSuffixForHeatExchanger(heatExchanger): return heatExchanger.displayName def exportDck(self): # pylint: disable=too-many-locals,too-many-statements if not self._checkConnExists(): msgb = QMessageBox() msgb.setText("Please connect all ports before exporting!") msgb.exec_() return noError = self._debugConn() if not noError: qmb = QMessageBox() qmb.setText("Ignore connection errors and continue with export?") qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) qmb.setDefaultButton(QMessageBox.Cancel) ret = qmb.exec() if ret == QMessageBox.Save: self.logger.debug("Overwriting") # continue else: self.logger.debug("Canceling") return nPorts = len(self.directPortPairs) nHx = len(self.heatExchangers) self.logger.debug("Storage Type: " + str(self.storageType)) self.logger.debug("nTes: " + str(self.nTes)) self.logger.debug("nPorts: " + str(nPorts)) self.logger.debug("nHx: " + str(nHx)) tool = Type1924_TesPlugFlow() inputs = { "nUnit": 50, "nType": self.storageType, "nTes": self.nTes, "nPorts": nPorts, "nHx": nHx, "nHeatSources": 1, } directPairsPorts = [] for directPortPair in self.directPortPairs: incomingConnection = directPortPair.fromPort.connectionList[0] temperatureName = "T" + incomingConnection.displayName massFlowRateName = "Mfr" + incomingConnection.displayName outgoingConnection = directPortPair.toPort.connectionList[0] reverseTemperatureName = "T" + outgoingConnection.displayName inputPos = directPortPair.relativeInputHeight outputPos = directPortPair.relativeOutputHeight directPairsPort = { "T": temperatureName, "side": directPortPair.side.formatDdck(), "Mfr": massFlowRateName, "Trev": reverseTemperatureName, "zIn": inputPos, "zOut": outputPos, } directPairsPorts.append(directPairsPort) heatExchangerPorts = [] for heatExchanger in self.heatExchangers: heatExchangerName = heatExchanger.displayName incomingConnection = heatExchanger.port1.connectionList[0] temperatureName = "T" + incomingConnection.displayName massFlowRateName = "Mfr" + incomingConnection.displayName outgoingConnection = heatExchanger.port2.connectionList[0] reverseTemperatureName = "T" + outgoingConnection.displayName inputPos = heatExchanger.relativeInputHeight outputPos = heatExchanger.relativeOutputHeight heatExchangerPort = { "Name": heatExchangerName, "T": temperatureName, "Mfr": massFlowRateName, "Trev": reverseTemperatureName, "zIn": inputPos, "zOut": outputPos, "cp": "cpwat", "rho": "rhowat", } heatExchangerPorts.append(heatExchangerPort) auxiliaryPorts = [] for _ in range(inputs["nHeatSources"]): dictInputAux = {"zAux": 0.0, "qAux": 0.0} auxiliaryPorts.append(dictInputAux) exportPath = _os.path.join(self.path, self.displayName + ".ddck") self.logger.debug(exportPath) tool.setInputs(inputs, directPairsPorts, heatExchangerPorts, auxiliaryPorts) tool.createDDck(self.path, self.displayName, typeFile="ddck") def _debugConn(self): self.logger.debug("Debugging conn") errorConnList = "" for directPort in self.directPortPairs: stFromPort = directPort.fromPort stToPort = directPort.toPort toPort1 = stFromPort.connectionList[0].toPort fromPort2 = stToPort.connectionList[0].fromPort connName1 = stFromPort.connectionList[0].displayName connName2 = stToPort.connectionList[0].displayName if stFromPort != toPort1: errorConnList = errorConnList + connName1 + "\n" if stToPort != fromPort2: errorConnList = errorConnList + connName2 + "\n" if errorConnList != "": msgBox = QMessageBox() msgBox.setText(f"{errorConnList} is connected wrongly, right click StorageTank to invert connection.") msgBox.exec() noError = False else: noError = True return noError def _checkConnExists(self): for heatExchanger in self.heatExchangers: if not heatExchanger.port1.connectionList: return False if not heatExchanger.port2.connectionList: return False for ports in self.leftDirectPortPairsPortItems: if not ports.connectionList: return False for ports in self.rightDirectPortPairsPortItems: if not ports.connectionList: return False return True def addTree(self): """ When a blockitem is added to the main window. A file explorer for that item is added to the right of the main window by calling this method """ pathName = self.displayName if self.parent.parent().projectPath == "": self.path = self.parent.parent().projectFolder else: self.path = self.parent.parent().projectPath self.path = _os.path.join(self.path, "ddck") self.path = _os.path.join(self.path, pathName) if not _os.path.exists(self.path): _os.makedirs(self.path) self.model = MyQFileSystemModel() self.model.setRootPath(self.path) self.model.setName(self.displayName) self.tree = MyQTreeView(self.model, self) self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(self.path)) self.tree.setObjectName(f"{self.displayName}Tree") self.tree.setMinimumHeight(200) self.tree.setSortingEnabled(True) self.parent.parent().splitter.addWidget(self.tree) def deleteBlock(self): """ Overridden method to also delete folder """ self.logger.debug("Block " + str(self) + " is deleting itself (" + self.displayName + ")") self.deleteConns() # self.logger.debug("self.parent.parent" + str(self.parent.parent())) self.parent.parent().trnsysObj.remove(self) self.logger.debug("deleting block " + str(self) + self.displayName) # self.logger.debug("self.scene is" + str(self.parent.scene())) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild(QTreeView, self.displayName + "Tree") _sh.rmtree(self.path) try: widgetToRemove.hide() except AttributeError: self.logger.debug("Widget doesnt exist!") else: self.logger.debug("Deleted widget") del self def setName(self, newName): """ Overridden method to also change folder name """ self.displayName = newName self.label.setPlainText(newName) self.model.setName(self.displayName) self.tree.setObjectName(f"{self.displayName}Tree") self.logger.debug(_os.path.dirname(self.path)) destPath = _os.path.join(_os.path.split(self.path)[0], self.displayName) if _os.path.split(self.path)[-1] == "" or _os.path.split(self.path)[-1] == "ddck": _os.makedirs(destPath) else: if _os.path.exists(self.path): _os.rename(self.path, destPath) self.path = destPath self.logger.debug(self.path)