• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SPF-OST / pytrnsys_gui / 11576810878

29 Oct 2024 03:09PM UTC coverage: 67.508% (-0.08%) from 67.591%
11576810878

push

github

web-flow
Merge pull request #564 from SPF-OST/560-black-change-line-length-to-pep8-standard-of-79-and-check-ci-reaction

changed line length in black to 79

1054 of 1475 new or added lines in 174 files covered. (71.46%)

150 existing lines in 74 files now uncovered.

10399 of 15404 relevant lines covered (67.51%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.19
/trnsysGUI/mainWindow.py
1
# type: ignore
2
# pylint: skip-file
3

4
import os
1✔
5
import pathlib as _pl
1✔
6
import shutil
1✔
7
import subprocess
1✔
8

9
import PyQt5.QtGui as _qtg
1✔
10
import PyQt5.QtWidgets as _qtw
1✔
11
import pytrnsys.utils.log as _ulog
1✔
12
import pytrnsys.utils.result as _res
1✔
13
import pytrnsys.utils.warnings as _warn
1✔
14
from PyQt5.QtWidgets import QAction
1✔
15

16
import trnsysGUI.configFileUpdater as _cfu
1✔
17
import trnsysGUI.diagram.export as _dexp
1✔
18
import trnsysGUI.loggingCallback as _lgcb
1✔
19
import trnsysGUI.menus.projectMenu.exportPlaceholders as _eph
1✔
20
from trnsysGUI import buildDck as buildDck
1✔
21
from trnsysGUI import constants
1✔
22
from trnsysGUI import images as _img
1✔
23
from trnsysGUI import project as _prj
1✔
24
from trnsysGUI import settings as _settings
1✔
25
from trnsysGUI import settingsDlg as _sdlg
1✔
26
from trnsysGUI import warningsAndErrors as _werrors
1✔
27
from trnsysGUI.BlockItem import BlockItem
1✔
28
from trnsysGUI.MassFlowVisualizer import MassFlowVisualizer
1✔
29
from trnsysGUI.ProcessMain import ProcessMain
1✔
30
from trnsysGUI.RunMain import RunMain
1✔
31
from trnsysGUI.common import cancelled as _ccl
1✔
32
from trnsysGUI.diagram import Editor as _de
1✔
33
from trnsysGUI.messageBox import MessageBox
1✔
34
from trnsysGUI.recentProjectsHandler import RecentProjectsHandler
1✔
35
from trnsysGUI.storageTank.widget import StorageTank
1✔
36

37

38
class MainWindow(_qtw.QMainWindow):
1✔
39
    def __init__(self, logger, project: _prj.Project, parent=None):
1✔
40
        super().__init__(parent)
1✔
41

42
        self.editor = None
1✔
43

44
        self.jsonPath = None
1✔
45
        self.logger = logger
1✔
46

47
        self.labelVisState = False
1✔
48
        self.calledByVisualizeMf = False
1✔
49
        self.currentFile = "Untitled"
1✔
50
        self.showBoxOnClose = True
1✔
51

52
        # Toolbar actions
53
        saveDiaAction = _qtw.QAction(_img.INBOX_PNG.icon(), "Save", self)
1✔
54
        saveDiaAction.triggered.connect(self.saveDia)
1✔
55

56
        loadDiaAction = _qtw.QAction(_img.OUTBOX_PNG.icon(), "Open", self)
1✔
57
        loadDiaAction.triggered.connect(self.loadDia)
1✔
58

59
        updateConfigAction = _qtw.QAction(
1✔
60
            _img.UPDATE_CONFIG_PNG.icon(), "Update run.config", self
61
        )
62
        updateConfigAction.triggered.connect(self.updateRun)
1✔
63

64
        runSimulationAction = _qtw.QAction(
1✔
65
            _img.RUN_SIMULATION_PNG.icon(), "Run simulation", self
66
        )
67
        runSimulationAction.triggered.connect(self.runSimulation)
1✔
68

69
        processSimulationAction = _qtw.QAction(
1✔
70
            _img.PROCESS_SIMULATION_PNG.icon(), "Process data", self
71
        )
72
        processSimulationAction.triggered.connect(self.processSimulation)
1✔
73

74
        deleteDiaAction = _qtw.QAction(
1✔
75
            _img.TRASH_PNG.icon(), "Delete diagram", self
76
        )
77
        deleteDiaAction.triggered.connect(self.deleteDia)
1✔
78

79
        zoomInAction = _qtw.QAction(_img.ZOOM_IN_PNG.icon(), "Zoom in", self)
1✔
80
        zoomInAction.triggered.connect(self.setZoomIn)
1✔
81

82
        zoomOutAction = _qtw.QAction(
1✔
83
            _img.ZOOM_OUT_PNG.icon(), "Zoom out", self
84
        )
85
        zoomOutAction.triggered.connect(self.setZoomOut)
1✔
86

87
        toggleConnLabels = _qtw.QAction(
1✔
88
            _img.LABEL_TOGGLE_PNG.icon(), "Toggle labels", self
89
        )
90
        toggleConnLabels.triggered.connect(self.toggleConnLabels)
1✔
91

92
        exportHydraulicsAction = _qtw.QAction(
1✔
93
            _img.EXPORT_HYDRAULICS_PNG.icon(), "Export hydraulic.ddck", self
94
        )
95
        exportHydraulicsAction.triggered.connect(self.exportHydraulicsDdck)
1✔
96

97
        exportHydCtrlAction = _qtw.QAction(
1✔
98
            _img.EXPORT_HYDRAULIC_CONTROL_PNG.icon(),
99
            "Export hydraulic_control.ddck",
100
            self,
101
        )
102
        exportHydCtrlAction.triggered.connect(self.exportHydraulicControl)
1✔
103

104
        exportDckAction = _qtw.QAction(
1✔
105
            _img.EXPORT_DCK_PNG.icon(), "Export dck", self
106
        )
107
        exportDckAction.triggered.connect(self.exportDck)
1✔
108

109
        toggleSnapAction = _qtw.QAction("Toggle snap grid", self)
1✔
110
        toggleSnapAction.triggered.connect(self.toggleSnap)
1✔
111
        toggleSnapAction.setShortcut("a")
1✔
112

113
        toggleAlignModeAction = _qtw.QAction("Toggle align mode", self)
1✔
114
        toggleAlignModeAction.triggered.connect(self.toggleAlignMode)
1✔
115
        toggleAlignModeAction.setShortcut("q")
1✔
116

117
        runMassflowSolverAction = _qtw.QAction(
1✔
118
            _img.RUN_MFS_PNG.icon(), "Run the massflow solver", self
119
        )
120
        runMassflowSolverAction.triggered.connect(self.runAndVisMf)
1✔
121

122
        openVisualizerAction = _qtw.QAction(
1✔
123
            _img.VIS_MFS_PNG.icon(), "Start visualization of mass flows", self
124
        )
125
        openVisualizerAction.triggered.connect(self.visualizeMf)
1✔
126

127
        # Tool bar
128
        tb = self.addToolBar("Main Toolbar...")
1✔
129
        tb.setObjectName("Toolbar")
1✔
130
        tb.addAction(saveDiaAction)
1✔
131
        tb.addAction(loadDiaAction)
1✔
132
        tb.addAction(zoomInAction)
1✔
133
        tb.addAction(zoomOutAction)
1✔
134
        tb.addAction(toggleConnLabels)
1✔
135
        tb.addAction(runMassflowSolverAction)
1✔
136
        tb.addAction(openVisualizerAction)
1✔
137
        tb.addAction(exportHydraulicsAction)
1✔
138
        tb.addAction(exportHydCtrlAction)
1✔
139
        tb.addAction(updateConfigAction)
1✔
140
        tb.addAction(exportDckAction)
1✔
141
        tb.addAction(runSimulationAction)
1✔
142
        tb.addAction(processSimulationAction)
1✔
143
        tb.addAction(deleteDiaAction)
1✔
144

145
        # Menu bar actions
146
        self.fileMenu = _qtw.QMenu("File")
1✔
147

148
        fileMenuNewAction = _qtw.QAction("New", self)
1✔
149
        fileMenuNewAction.triggered.connect(self.newDia)
1✔
150
        fileMenuNewAction.setShortcut("Ctrl+n")
1✔
151
        self.fileMenu.addAction(fileMenuNewAction)
1✔
152

153
        fileMenuOpenAction = _qtw.QAction("Open", self)
1✔
154
        fileMenuOpenAction.triggered.connect(self.openFile)
1✔
155
        fileMenuOpenAction.setShortcut("Ctrl+o")
1✔
156
        self.fileMenu.addAction(fileMenuOpenAction)
1✔
157

158
        self.recentProjectsMenu = _qtw.QMenu("Recent Projects")
1✔
159
        # ===============================================================================
160
        # Must be monospaced font to ensure paths align nicely.
161
        self.recentProjectsMenu.setFont(
1✔
162
            _qtg.QFont(constants.DEFAULT_MONOSPACED_FONT)
163
        )
164
        # ===============================================================================
165
        self.recentProjectsMenu.triggered.connect(self.openRecentFile)
1✔
166
        self.fileMenu.addMenu(self.recentProjectsMenu)
1✔
167
        self.updateRecentFileMenu(project.jsonFilePath)
1✔
168

169
        fileMenuSaveAction = _qtw.QAction("Save", self)
1✔
170
        fileMenuSaveAction.triggered.connect(self.saveDia)
1✔
171
        fileMenuSaveAction.setShortcut("Ctrl+s")
1✔
172
        self.fileMenu.addAction(fileMenuSaveAction)
1✔
173

174
        fileMenuCopyToNewAction = _qtw.QAction("Copy to new folder", self)
1✔
175
        fileMenuCopyToNewAction.triggered.connect(self.copyToNew)
1✔
176
        self.fileMenu.addAction(fileMenuCopyToNewAction)
1✔
177

178
        exportDiagram = _qtw.QAction("Export diagram", self)
1✔
179
        exportDiagram.triggered.connect(self.exportDiagram)
1✔
180
        exportDiagram.setShortcut("Ctrl+e")
1✔
181
        self.fileMenu.addAction(exportDiagram)
1✔
182

183
        debugConnections = _qtw.QAction("Debug Conn", self)
1✔
184
        debugConnections.triggered.connect(self.debugConns)
1✔
185
        self.fileMenu.addAction(debugConnections)
1✔
186

187
        def askForSaveAndLoadSettings() -> None:
1✔
188
            self.askUserForSettingsValuesAndSave()
×
189
            self.loadTrnsysPath()
×
190

191
        setTransysPath = _qtw.QAction("Set TRNSYS path", self)
1✔
192
        setTransysPath.triggered.connect(askForSaveAndLoadSettings)
1✔
193
        self.fileMenu.addAction(setTransysPath)
1✔
194

195
        self.editMenu = _qtw.QMenu("Edit")
1✔
196
        self.editMenu.addAction(toggleSnapAction)
1✔
197
        self.editMenu.addAction(toggleAlignModeAction)
1✔
198

199
        runMassflowSolverActionMenu = _qtw.QAction(
1✔
200
            "Run mass flow solver", self
201
        )
202
        runMassflowSolverActionMenu.triggered.connect(self.runAndVisMf)
1✔
203

204
        openVisualizerActionMenu = _qtw.QAction(
1✔
205
            "Start mass flow visualizer", self
206
        )
207
        openVisualizerActionMenu.triggered.connect(self.visualizeMf)
1✔
208

209
        exportHydraulicsActionMenu = _qtw.QAction(
1✔
210
            "Export hydraulic.ddck", self
211
        )
212
        exportHydraulicsActionMenu.triggered.connect(self.exportHydraulicsDdck)
1✔
213

214
        exportHydCtrlActionMenu = _qtw.QAction(
1✔
215
            "Export hydraulic_control.ddck", self
216
        )
217
        exportHydCtrlActionMenu.triggered.connect(self.exportHydraulicControl)
1✔
218

219
        exportDdckPlaceHolderValuesJsonFileActionMenu = _qtw.QAction(
1✔
220
            "Export ddck placeholder values JSON file", self
221
        )
222
        exportDdckPlaceHolderValuesJsonFileActionMenu.triggered.connect(
1✔
223
            self.exportDdckPlaceHolderValuesJson
224
        )
225

226
        updateConfigActionMenu = _qtw.QAction("Update run.config", self)
1✔
227
        updateConfigActionMenu.triggered.connect(self.updateRun)
1✔
228

229
        exportDckActionMenu = _qtw.QAction("Export dck", self)
1✔
230
        exportDckActionMenu.triggered.connect(self.exportDck)
1✔
231

232
        runSimulationActionMenu = _qtw.QAction("Run simulation...", self)
1✔
233
        runSimulationActionMenu.triggered.connect(self.runSimulation)
1✔
234

235
        processSimulationActionMenu = _qtw.QAction(
1✔
236
            "Process simulation...", self
237
        )
238
        processSimulationActionMenu.triggered.connect(self.processSimulation)
1✔
239

240
        self.projectMenu = _qtw.QMenu("Project")
1✔
241
        self.projectMenu.addAction(runMassflowSolverActionMenu)
1✔
242
        self.projectMenu.addAction(openVisualizerActionMenu)
1✔
243
        self.projectMenu.addAction(exportHydraulicsActionMenu)
1✔
244
        self.projectMenu.addAction(exportHydCtrlActionMenu)
1✔
245
        self.projectMenu.addAction(
1✔
246
            exportDdckPlaceHolderValuesJsonFileActionMenu
247
        )
248
        self.projectMenu.addAction(updateConfigActionMenu)
1✔
249
        self.projectMenu.addAction(exportDckActionMenu)
1✔
250
        self.projectMenu.addAction(runSimulationActionMenu)
1✔
251
        self.projectMenu.addAction(processSimulationActionMenu)
1✔
252

253
        pytrnsysOnlineDocAction = _qtw.QAction(
1✔
254
            "pytrnsys online documentation", self
255
        )
256
        pytrnsysOnlineDocAction.triggered.connect(self.openPytrnsysOnlineDoc)
1✔
257

258
        self.helpMenu = _qtw.QMenu("Help")
1✔
259
        self.helpMenu.addAction(pytrnsysOnlineDocAction)
1✔
260

261
        # Menu bar
262
        self.mb = self.menuBar()
1✔
263
        self.mb.addMenu(self.fileMenu)
1✔
264
        self.mb.addMenu(self.projectMenu)
1✔
265
        self.mb.addMenu(self.editMenu)
1✔
266
        self.mb.addMenu(self.helpMenu)
1✔
267
        self.mb.addSeparator()
1✔
268

269
        # Status bar
270
        self.sb = self.statusBar()
1✔
271

272
        # QUndo framework
273
        self.undoStack = _qtw.QUndoStack(self)
1✔
274
        undoAction = self.undoStack.createUndoAction(self, "Undo")
1✔
275
        undoAction.setShortcut("Ctrl+z")
1✔
276

277
        redoAction = self.undoStack.createRedoAction(self, "Redo")
1✔
278
        redoAction.setShortcut("Ctrl+y")
1✔
279

280
        self.editMenu.addAction(undoAction)
1✔
281
        self.editMenu.addAction(redoAction)
1✔
282

283
        self._resetEditor(project)
1✔
284

285
    def isRunning(self):
1✔
286
        return self.editor.isRunning()
×
287

288
    def start(self) -> None:
1✔
NEW
289
        _lgcb.configureLoggingCallback(
×
290
            self.logger, self._loggingCallback, _ulog.FORMAT
291
        )
UNCOV
292
        self.editor.start()
×
293

294
    def shutdown(self) -> None:
1✔
295
        _lgcb.removeLoggingCallback(self.logger, self._loggingCallback)
×
296
        self.editor.shutdown()
×
297

298
    def newDia(self):
1✔
NEW
299
        if (
×
300
            MessageBox.create(messageText=constants.UNSAVED_PROGRESS_LOST)
301
            == _qtw.QMessageBox.Cancel
302
        ):
UNCOV
303
            return
×
304

305
        startingDirectoryPath = self._projectDirPath.parent
×
306

NEW
307
        createProjectMaybeCancelled = _prj.getCreateProject(
×
308
            startingDirectoryPath
309
        )
310
        if _ccl.isCancelled(createProjectMaybeCancelled):
×
311
            return
×
312

313
        createProject = _ccl.value(createProjectMaybeCancelled)
×
314

315
        self.updateRecentFileMenu(createProject.jsonFilePath)
×
316
        self._resetEditor(createProject)
×
317

318
    def saveDia(self):
1✔
319
        self.logger.info("Saving diagram")
×
320
        self.editor.saveProject()
×
321

322
    def copyToNew(self):
1✔
323
        currentProjectFolderPath = self._projectDirPath
×
324

325
        startingDirectoryPath = currentProjectFolderPath.parent
×
326

327
        maybeCancelled = _prj.getExistingEmptyDirectory(startingDirectoryPath)
×
328
        if _ccl.isCancelled(maybeCancelled):
×
329
            return
×
330
        newProjectFolderPath = _ccl.value(maybeCancelled)
×
331

332
        oldProjectFolderPath = self._projectDirPath
×
333

NEW
334
        self.copyContentsToNewFolder(
×
335
            newProjectFolderPath, oldProjectFolderPath
336
        )
337

338
    def copyContentsToNewFolder(
1✔
339
        self, newProjectFolderPath, oldProjectFolderPath
340
    ):
341
        self._copyContents(oldProjectFolderPath, newProjectFolderPath)
1✔
342
        newJsonFilePath = self._changeAndGetNewJsonFileName(
1✔
343
            newProjectFolderPath, oldProjectFolderPath
344
        )
345

346
        loadProject = _prj.LoadProject(newJsonFilePath)
1✔
347

348
        self._resetEditor(loadProject)
1✔
349

350
        self.editor.saveProject(showWarning=False)
1✔
351

352
    @staticmethod
1✔
353
    def _copyContents(oldProjectFolderPath, newProjectFolderPath):
1✔
354
        for child in oldProjectFolderPath.iterdir():
1✔
355
            destinationPath = newProjectFolderPath / child.name
1✔
356
            if child.is_dir():
1✔
357
                shutil.copytree(child, destinationPath)
1✔
358
            else:
359
                shutil.copy(child, destinationPath)
1✔
360

361
    @staticmethod
1✔
362
    def _changeAndGetNewJsonFileName(
1✔
363
        newProjectFolderPath, oldProjectFolderPath
364
    ):
365
        oldJsonFileName = f"{oldProjectFolderPath.name}.json"
1✔
366
        newJsonFileName = f"{newProjectFolderPath.name}.json"
1✔
367
        shutil.move(
1✔
368
            newProjectFolderPath / oldJsonFileName,
369
            newProjectFolderPath / newJsonFileName,
370
        )
371
        newJsonFilePath = newProjectFolderPath / newJsonFileName
1✔
372
        return newJsonFilePath
1✔
373

374
    def loadDia(self):
1✔
375
        self.logger.info("Loading diagram")
×
376
        self.openFile()
×
377

378
    def updateRun(self):
1✔
379
        configFilePath = self._projectDirPath / "run.config"
×
380

381
        if configFilePath.is_dir():
×
382
            _qtw.QMessageBox.information(
×
383
                self,
384
                "`run.config` is a directory",
385
                f"Could not create file {configFilePath} because a directory of the same name exists. "
386
                "Please remove or rename the directory before trying to update the `run.config` file.",
387
            )
388
            return
×
389

390
        configFilePath.touch()
×
391

392
        runConfig = _cfu.ConfigFileUpdater(configFilePath)
×
393
        runConfig.updateConfig()
×
394

395
    @property
1✔
396
    def _projectDirPath(self) -> _pl.Path:
1✔
397
        return _pl.Path(self.projectFolder)
1✔
398

399
    def runSimulation(self):
1✔
400
        ddckPath = os.path.join(self.projectFolder, "ddck")
×
401

402
        #   Check hydraulic.ddck
403
        hydraulicPath = os.path.join(ddckPath, "hydraulic\\hydraulic.ddck")
×
404
        if not os.path.isfile(hydraulicPath):
×
405
            self.exportHydraulicsDdck()
×
406

407
        #   Check ddcks of storage tanks
408
        storageWithoutFile = []
×
409
        for object in self.editor.trnsysObj:
×
410
            if isinstance(object, StorageTank):
×
NEW
411
                storageTankFile = os.path.join(
×
412
                    object.displayName, object.displayName + ".ddck"
413
                )
414
                storageTankPath = os.path.join(ddckPath, storageTankFile)
×
415
                if not (os.path.isfile(storageTankPath)):
×
416
                    storageWithoutFile.append(object.displayName + "\n")
×
417

418
        if storageWithoutFile:
×
419
            errorMessage = "The following storage tank(s) do(es) not have a corresponding ddck:\n\n"
×
420
            for storage in storageWithoutFile:
×
421
                errorMessage += storage
×
NEW
422
            errorMessage += "\nPlease make sure you that you export the ddck for every storage tank before starting a simulation."
×
UNCOV
423
            _werrors.showMessageBox(errorMessage)
×
424
            return
×
425

426
        #   Update run.config
427
        self.updateRun()
×
428

429
        #   Start simulation
430
        runApp = RunMain()
×
NEW
431
        executionFailed, errorStatement = runApp.runAction(
×
432
            self.logger, self.editor.projectFolder
433
        )
434

435
        if executionFailed:
×
436
            errorMessage = f"Exception while trying to execute RunParallelTrnsys:\n\n{errorStatement}"
×
437
            _werrors.showMessageBox(errorMessage)
×
438

439
        return
×
440

441
    def processSimulation(self):
1✔
442
        processPath = os.path.join(self.projectFolder, "process.config")
×
443
        if not os.path.isfile(processPath):
×
444
            errorMessage = f"No such file: {processPath}"
×
445
            _werrors.showMessageBox(errorMessage)
×
446
            return
×
447
        processApp = ProcessMain()
×
NEW
448
        result = processApp.processAction(
×
449
            self.logger, self.editor.projectFolder
450
        )
451

452
        if _res.isError(result):
×
453
            error = _res.error(result)
×
454
            _werrors.showMessageBox(error.message)
×
455

456
        return
×
457

458
    def exportDdckPlaceHolderValuesJson(self):
1✔
459
        result = _eph.exportDdckPlaceHolderValuesJsonFile(self.editor)
×
460
        if _res.isError(result):
×
NEW
461
            errorMessage = (
×
462
                f"The json file could not be generated: {result.message}"
463
            )
UNCOV
464
            _werrors.showMessageBox(errorMessage)
×
465

466
    def renameDia(self):
1✔
467
        self.logger.info("Renaming diagram...")
×
468
        self.editor.showDiagramDlg()
×
469

470
    def deleteDia(self):
1✔
471
        qmb = _qtw.QMessageBox()
×
NEW
472
        qmb.setText(
×
473
            'Are you sure you want to delete the diagram? (There is no possibility to "undo".)'
474
        )
475
        qmb.setStandardButtons(_qtw.QMessageBox.Yes | _qtw.QMessageBox.Cancel)
×
476
        qmb.setDefaultButton(_qtw.QMessageBox.Cancel)
×
477
        ret = qmb.exec()
×
478
        if ret == _qtw.QMessageBox.Yes:
×
479
            self.logger.info("Deleting diagram")
×
480
            self.editor.delBlocks()
×
481
        else:
482
            self.logger.info("Canceling")
×
483
            return
×
484

485
    def setZoomIn(self):
1✔
486
        self.logger.info("Setting zoom in")
×
487
        self.editor.diagramView.scale(1.2, 1.2)
×
488

489
    def setZoomOut(self):
1✔
490
        self.logger.info("Setting zoom out")
×
491
        self.editor.diagramView.scale(0.8, 0.8)
×
492

493
    def setZoom0(self):
1✔
494
        self.logger.info("Setting zoom 0")
×
495
        self.editor.diagramView.resetTransform()
×
496

497
    def runAndVisMf(self):
1✔
498
        self.calledByVisualizeMf = True
×
499
        filePaths = self.runMassflowSolver()
×
500
        if not filePaths:
×
501
            self.logger.error("Could not execute runMassflowSolver")
×
502
            return
×
503

504
        mfrFile, tempFile = filePaths
×
505
        if not os.path.isfile(mfrFile) or not os.path.isfile(tempFile):
×
506
            self.logger.error("No mfrFile or tempFile found!")
×
507
            return
×
508

509
        MassFlowVisualizer(self, mfrFile, tempFile)
×
510

511
    def visualizeMf(self):
1✔
512
        qmb = _qtw.QMessageBox()
×
NEW
513
        qmb.setText(
×
514
            "Please select the mass flow rate prt-file that you want to visualize."
515
        )
516
        qmb.setStandardButtons(_qtw.QMessageBox.Ok)
×
517
        qmb.setDefaultButton(_qtw.QMessageBox.Ok)
×
518
        qmb.exec()
×
519

NEW
520
        mfrFile = _qtw.QFileDialog.getOpenFileName(
×
521
            self, "Open diagram", filter="*Mfr.prt"
522
        )[0].replace("/", "\\")
523
        tempFile = mfrFile.replace("Mfr", "T")
×
524
        self.calledByVisualizeMf = True
×
525
        if os.path.isfile(mfrFile) and os.path.isfile(tempFile):
×
526
            MassFlowVisualizer(self, mfrFile, tempFile)
×
527
        else:
528
            self.logger.info("No mfrFile or tempFile found!")
×
529

530
    def openFile(self):
1✔
531
        self.logger.info("Opening diagram")
×
532

NEW
533
        if (
×
534
            MessageBox.create(messageText=constants.UNSAVED_PROGRESS_LOST)
535
            == _qtw.QMessageBox.Cancel
536
        ):
UNCOV
537
            return
×
538

539
        maybeCancelled = _prj.getLoadOrMigrateProject()
×
540
        if _ccl.isCancelled(maybeCancelled):
×
541
            return
×
542
        project = _ccl.value(maybeCancelled)
×
543
        RecentProjectsHandler.addProject(project.jsonFilePath)
×
544
        self.updateRecentFileMenu(project.jsonFilePath)
×
545
        self._resetEditor(project)
×
546

547
        if isinstance(project, _prj.MigrateProject):
×
548
            self.editor.saveProject()
×
549

550
    def openRecentFile(self, actionClicked: QAction):
1✔
551
        if (
1✔
552
            MessageBox.create(messageText=constants.UNSAVED_PROGRESS_LOST)
553
            == _qtw.QMessageBox.Cancel
554
        ):
UNCOV
555
            return
×
556

557
        maybeCancelled = _prj.loadRecentProject(actionClicked.data())
1✔
558
        if _ccl.isCancelled(maybeCancelled):
1✔
559
            self.recentProjectsMenu.removeAction(actionClicked)
×
560
            return
×
561

562
        project = _ccl.value(maybeCancelled)
1✔
563
        RecentProjectsHandler.addProject(project.jsonFilePath)
1✔
564
        self.updateRecentFileMenu(project.jsonFilePath)
1✔
565
        self._resetEditor(project)
1✔
566

567
    def _resetEditor(self, project):
1✔
568
        wasRunning = self.editor and self.editor.isRunning()
1✔
569

570
        self.undoStack.clear()
1✔
571

572
        self.editor = self._createDiagramEditor(project)
1✔
573
        self.setCentralWidget(self.editor)
1✔
574

575
        if wasRunning:
1✔
576
            self.editor.start()
×
577

578
    def toggleConnLabels(self):
1✔
579
        self.labelVisState = not self.labelVisState
×
580
        self.editor.setConnLabelVis(self.labelVisState)
×
581

582
    def exportHydraulicsDdck(self):
1✔
583
        self.editor.exportHydraulics(exportTo="ddck")
×
584

585
    def exportHydraulicControl(self):
1✔
586
        self.editor.exportHydraulicControl()
×
587

588
    def exportDck(self):
1✔
589
        jsonResult = _eph.exportDdckPlaceHolderValuesJsonFile(self.editor)
1✔
590
        if _res.isError(jsonResult):
1✔
591
            errorMessage = f"The placeholder values JSON file could not be generated: {jsonResult.message}"
×
592
            _werrors.showMessageBox(errorMessage)
×
593
            return
×
594

595
        builder = buildDck.DckBuilder(self._projectDirPath)
1✔
596

597
        result = builder.buildTrnsysDeck()
1✔
598
        if _res.isError(result):
1✔
NEW
599
            errorMessage = (
×
600
                f"The deck file could not be generated: {result.message}"
601
            )
602
            _werrors.showMessageBox(errorMessage)
×
603
            return
×
604
        warnings: _warn.ValueWithWarnings[str | None] = _res.value(result)
1✔
605

606
        if warnings.hasWarnings():
1✔
607
            warningMessage = warnings.toWarningMessage()
×
608
            _werrors.showMessageBox(warningMessage, _werrors.Title.WARNING)
×
609

610
    def toggleAlignMode(self):
1✔
611
        self.logger.info("Toggling alignMode")
×
612
        self.editor.alignMode = not self.editor.alignMode
×
613

614
    def toggleSnap(self):
1✔
615
        self.editor.toggleSnap()
×
616

617
    def runMassflowSolver(self):
1✔
618
        self.logger.info("Running massflow solver...")
×
619

620
        exportPath = self.editor.exportHydraulics(exportTo="mfs")
×
621
        if not exportPath:
×
622
            return None
×
623

624
        self.exportedTo = exportPath
×
625
        self.logger.info(exportPath)
×
626

627
        if not self.editor.trnsysPath.is_file():
×
628
            errorMessage = "TRNExe.exe not found! Consider correcting the path in the settings."
×
629
            _werrors.showMessageBox(errorMessage)
×
630
            return None
×
631

632
        try:
×
NEW
633
            subprocess.run(
×
634
                [str(self.editor.trnsysPath), exportPath, "/H"], check=True
635
            )
NEW
636
            mfrFile = os.path.join(
×
637
                self.projectFolder,
638
                self.projectFolder.split("\\")[-1] + "_Mfr.prt",
639
            )
NEW
640
            tempFile = os.path.join(
×
641
                self.projectFolder,
642
                self.projectFolder.split("\\")[-1] + "_T.prt",
643
            )
644
            self.calledByVisualizeMf = False
×
645
            return mfrFile, tempFile
×
646
        except Exception as exception:
×
647
            errorMessage = f"An exception occurred while trying to execute the mass flow solver: {exception}"
×
648
            _werrors.showMessageBox(errorMessage)
×
649
            self.logger.error(errorMessage)
×
650

651
            return None
×
652

653
    def movePorts(self):
1✔
654
        self.editor.moveDirectPorts = True
×
655

656
    def openPytrnsysOnlineDoc(self):
1✔
657
        os.system('start "" https://pytrnsys.readthedocs.io')
×
658

659
    def exportDiagram(self):
1✔
660
        fileName, _ = _qtw.QFileDialog.getSaveFileName(
×
661
            self,
662
            "Export PDF",
663
            None,
664
            "PDF files (*.pdf);;SVG files (*.svg);;All Files (*)",
665
            "PDF files (*.svg)",
666
        )
667
        if fileName == "":
×
668
            return
×
669

670
        if _dexp.getExtension(fileName) == "":
×
671
            fileName += ".pdf"
×
672

673
        _dexp.export(self.editor.diagramScene, fileName)
×
674

675
    def closeEvent(self, e):
1✔
676
        if self.showBoxOnClose:
1✔
677
            ret = MessageBox.create(
×
678
                messageText=constants.SAVE_BEFORE_CLOSE,
679
                buttons=[
680
                    _qtw.QMessageBox.Save,
681
                    _qtw.QMessageBox.Close,
682
                    _qtw.QMessageBox.Cancel,
683
                ],
684
                defaultButton=_qtw.QMessageBox.Cancel,
685
            )
686
            if ret == _qtw.QMessageBox.Cancel:
×
687
                e.ignore()
×
688
                return
×
689
            if ret == _qtw.QMessageBox.Save:
×
690
                self.editor.saveProject()
×
691

692
            RecentProjectsHandler.save()
×
693
            e.accept()
×
694

695
    def ensureSettingsExist(self):
1✔
696
        if not _settings.Settings.tryLoadOrNone():
×
697
            self.askUserForSettingsValuesAndSave()
×
698

699
    def askUserForSettingsValuesAndSave(self):
1✔
700
        newSettings = _sdlg.SettingsDlg.showDialogAndGetSettings(parent=self)
×
701
        while newSettings == _sdlg.CANCELLED:
×
NEW
702
            newSettings = _sdlg.SettingsDlg.showDialogAndGetSettings(
×
703
                parent=self
704
            )
UNCOV
705
        newSettings.save()
×
706

707
    def loadTrnsysPath(self):
1✔
708
        settings = _settings.Settings.load()
×
709
        self.editor.trnsysPath = _pl.Path(settings.trnsysBinaryPath)
×
710

711
    def debugConns(self):
1✔
712
        """
713
        Check each block items for error connections.
714
        Returns warning message if blockitem contains two input connections or two output connections
715
        """
716
        self.logger.info("trnsysObjs:", self.editor.trnsysObj)
×
717
        self.noErrorConns = True
×
718
        for o in self.editor.trnsysObj:
×
719
            self.logger.info(o)
×
NEW
720
            if (
×
721
                isinstance(o, BlockItem)
722
                and len(o.outputs) == 1
723
                and len(o.inputs) == 1
724
            ):
725
                self.logger.info("Checking block connections", o.displayName)
×
726
                objInput = o.inputs[0]
×
727
                objOutput = o.outputs[0]
×
728
                connToInputToPort = objInput.connectionList[0].toPort
×
729
                connToOutputToPort = objOutput.connectionList[0].toPort
×
730
                connToInputFromPort = objInput.connectionList[0].fromPort
×
731
                connToOutputFromPort = objOutput.connectionList[0].fromPort
×
732
                connName1 = objInput.connectionList[0].displayName
×
733
                connName2 = objOutput.connectionList[0].displayName
×
734
                objName = o.displayName
×
735

NEW
736
                if (
×
737
                    objInput == connToInputToPort
738
                    and objOutput == connToOutputToPort
739
                ):
740
                    msgBox = _qtw.QMessageBox()
×
NEW
741
                    msgBox.setText(
×
742
                        "both %s and %s are input ports into %s"
743
                        % (connName1, connName2, objName)
744
                    )
745
                    msgBox.exec_()
×
746
                    self.noErrorConns = False
×
747

NEW
748
                elif (
×
749
                    objInput == connToInputFromPort
750
                    and objOutput == connToOutputFromPort
751
                ):
752
                    msgBox = _qtw.QMessageBox()
×
NEW
753
                    msgBox.setText(
×
754
                        "both %s and %s are output ports from %s"
755
                        % (connName1, connName2, objName)
756
                    )
757
                    msgBox.exec_()
×
758
                    self.noErrorConns = False
×
759
        return self.noErrorConns
×
760

761
    def _createDiagramEditor(self, project: _prj.Project) -> _de.Editor:
1✔
762
        if isinstance(project, _prj.LoadProject):
1✔
763
            self.projectFolder = str(project.jsonFilePath.parent)
1✔
764
            return _de.Editor(
1✔
765
                self,
766
                str(project.jsonFilePath.parent),
767
                jsonPath=None,
768
                loadValue="load",
769
                logger=self.logger,
770
            )
771

772
        if isinstance(project, _prj.MigrateProject):
×
773
            self.projectFolder = str(project.newProjectFolderPath)
×
774
            return _de.Editor(
×
775
                self,
776
                str(project.newProjectFolderPath),
777
                str(project.oldJsonFilePath),
778
                loadValue="json",
779
                logger=self.logger,
780
            )
781

782
        if isinstance(project, _prj.CreateProject):
×
783
            self.projectFolder = str(project.jsonFilePath.parent)
×
784
            return _de.Editor(
×
785
                self,
786
                self.projectFolder,
787
                jsonPath=str(project.jsonFilePath),
788
                loadValue="new",
789
                logger=self.logger,
790
            )
791

792
        raise AssertionError(f"Unknown `project' type: {type(project)}")
×
793

794
    def _loggingCallback(self, logMessage: str) -> None:
1✔
795
        self.editor.loggingTextEdit.appendPlainText(logMessage)
×
796

797
    def updateRecentFileMenu(self, currentProject: _pl.Path):
1✔
798
        self.recentProjectsMenu.clear()
1✔
799
        maxLength = RecentProjectsHandler.getLengthOfLongestFileName()
1✔
800
        for recentProject in RecentProjectsHandler.recentProjects:
1✔
801
            if recentProject != currentProject:
1✔
802
                formattedFileName = recentProject.stem.ljust(maxLength)
1✔
803
                recentProjectAction = _qtw.QAction(
1✔
804
                    f"{formattedFileName} {recentProject}", self
805
                )
806
                recentProjectAction.setData(recentProject)
1✔
807
                self.recentProjectsMenu.addAction(recentProjectAction)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc