Source code for pymoskito.simulation_gui

# -*- coding: utf-8 -*-

# system
import logging
import numpy as np
import os
import pickle
import time
import pkg_resources
import webbrowser
import yaml
from operator import itemgetter
from scipy.interpolate import interp1d

# Qt
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QTimer, QSize, QSettings,
                          QCoreApplication, QModelIndex, QRectF)
from PyQt5.QtGui import QIcon, QKeySequence, QColor
from PyQt5.QtWidgets import (
    QApplication, QMainWindow,
    QWidget, QAction, QSlider, QLabel, QFrame, QPushButton,
    QListWidget, QListWidgetItem, QTreeView, QTreeWidget, QTreeWidgetItem,
    QAbstractItemView,
    QToolBar, QStatusBar, QProgressBar,
    QTextEdit, QFileDialog, QInputDialog,
    QVBoxLayout, QHBoxLayout, QMessageBox,
)

# pyqtgraph
import pyqtgraph as pg
from pyqtgraph.dockarea import DockArea

# vtk
vtk_error_msg = ""
try:
    import vtk

    from vtk import vtkRenderer
    from vtk import qt
    # import patched class that fixes scroll problem
    from .visualization import QVTKRenderWindowInteractor

    vtk_available = True
except ImportError as e:
    vtk_available = False
    vtk_error_msg = e
    vtkRenderer = None
    QVTKRenderWindowInteractor = None

# pymoskito
from .registry import get_registered_visualizers
from .simulation_interface import SimulatorInteractor, SimulatorView
from .visualization import MplVisualizer, VtkVisualizer
from .processing_gui import PostProcessor
from .tools import get_resource, PlainTextLogger, LengthList, Exporter

__all__ = ["SimulationGui", "run"]


[docs]def run(regimes=None): """ Helper function to launch the PyMoskito GUI """ app = QApplication([]) prog = SimulationGui() if regimes is not None: prog.load_regimes_from_file(regimes) prog.show() app.exec_()
[docs]class SimulationGui(QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name TABLEAU_COLORS = ( ('blue', '#1f77b4'), ('orange', '#ff7f0e'), ('green', '#2ca02c'), ('red', '#d62728'), ('purple', '#9467bd'), ('brown', '#8c564b'), ('pink', '#e377c2'), ('gray', '#7f7f7f'), ('olive', '#bcbd22'), ('cyan', '#17becf'), ) runSimulation = pyqtSignal() stopSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal(bool) def __init__(self): # constructor of the base class QMainWindow.__init__(self) QCoreApplication.setOrganizationName("RST") QCoreApplication.setOrganizationDomain("https://tu-dresden.de/rst") QCoreApplication.setApplicationVersion( pkg_resources.require("PyMoskito")[0].version) QCoreApplication.setApplicationName(globals()["__package__"]) # load settings self._settings = QSettings() self._init_settings() # initialize logger self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.stopSimulation.connect(self.sim.stop_simulation) self.sim.simulation_finalized.connect(self.new_simulation_data) self.currentDataset = None self.interpolator = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties icon_size = QSize(25, 25) self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.animationDock = pg.dockarea.Dock("Animation") self.regimeDock = pg.dockarea.Dock("Regimes") self.lastSimDock = pg.dockarea.Dock("Last Simulations") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") # arrange docks self.area.addDock(self.animationDock, "right") self.area.addDock(self.lastSimDock, "left", self.animationDock) self.area.addDock(self.propertyDock, "bottom", self.lastSimDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.logDock, "bottom", self.dataDock) self.area.addDock(self.regimeDock, "left", self.lastSimDock) self.non_plotting_docks = list(self.area.findAll()[1].keys()) self.standardDockState = self.area.saveState() # add widgets to the docks self.propertyDock.addWidget(self.targetView) if not vtk_available: self._logger.warning("loading vtk failed with:{}".format(vtk_error_msg)) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format( [name for cls, name in available_vis])) if available_vis: # instantiate the first visualizer self._logger.info("loading visualizer '{}'".format(available_vis[0][1])) self.animationLayout = QVBoxLayout() if issubclass(available_vis[0][0], MplVisualizer): self.animationWidget = QWidget() self.visualizer = available_vis[0][0](self.animationWidget, self.animationLayout) self.animationDock.addWidget(self.animationWidget) elif issubclass(available_vis[0][0], VtkVisualizer): if vtk_available: # vtk window self.animationFrame = QFrame() self.vtkWidget = QVTKRenderWindowInteractor( self.animationFrame) self.animationLayout.addWidget(self.vtkWidget) self.animationFrame.setLayout(self.animationLayout) self.animationDock.addWidget(self.animationFrame) self.vtk_renderer = vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer( self.vtk_renderer) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self._logger.warning("visualizer depends on vtk which is " "not available on this system!") elif available_vis: raise NotImplementedError else: self.visualizer = None # regime window self.regime_list = QListWidget(self) self.regime_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" self.actDeleteRegimes = QAction(self.regime_list) self.actDeleteRegimes.setText("&Delete Selected Regimes") # TODO shortcut works always, not only with focus on the regime list # self.actDeleteRegimes.setShortcutContext(Qt.WindowShortcut) self.actDeleteRegimes.setShortcut(QKeySequence(Qt.Key_Delete)) self.actDeleteRegimes.triggered.connect(self.remove_regime_items) self.actSave = QAction(self) self.actSave.setText('Save Results As') self.actSave.setIcon(QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.setShortcut(QKeySequence.Save) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QAction(self) self.actLoadRegimes.setText("Load Regimes from File") self.actLoadRegimes.setIcon(QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.setShortcut(QKeySequence.Open) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExitOnBatchCompletion = QAction(self) self.actExitOnBatchCompletion.setText("&Exit On Batch Completion") self.actExitOnBatchCompletion.setCheckable(True) self.actExitOnBatchCompletion.setChecked( self._settings.value("control/exit_on_batch_completion") == "True" ) self.actExitOnBatchCompletion.changed.connect( self.update_exit_on_batch_completion_setting) # regime management self.runningBatch = False self._current_regime_index = None self._current_regime_name = None self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # last sim window self.lastSimList = QListWidget(self) self.lastSimDock.addWidget(self.lastSimList) self._lastSimulations = LengthList(20) self.lastSimList.itemDoubleClicked.connect(self.load_last_sim) # data window self.dataWidget = QWidget() self.dataLayout = QHBoxLayout() self.dataPointListWidget = QListWidget() self.dataPointListLayout = QVBoxLayout() self.dataPointListWidget.setLayout(self.dataPointListLayout) self.dataPointListWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.dataLayout.addWidget(self.dataPointListWidget) self.dataPointManipulationWidget = QWidget() self.dataPointManipulationLayout = QVBoxLayout() self.dataPointManipulationLayout.addStretch() self.dataPointManipulationLayout.setSpacing(5) self.dataPointManipulationWidget.setLayout(self.dataPointManipulationLayout) self.dataLayout.addWidget(self.dataPointManipulationWidget) self.dataPointLabel = QLabel("Data", self) self.dataPointLabel.setAlignment(Qt.AlignCenter) self.dataPointManipulationLayout.addWidget(self.dataPointLabel) self.dataPointAddButton = QPushButton(self) self.dataPointAddButton.setIcon(QIcon(get_resource("add.png"))) self.dataPointAddButton.setToolTip( "Add the selected data set from the left to the selected plot " "on the right.") self.dataPointAddButton.clicked.connect(self.addDatapointToTree) self.dataPointManipulationLayout.addWidget(self.dataPointAddButton) self.dataPointRemoveButton = QPushButton(self) self.dataPointRemoveButton.setIcon(QIcon(get_resource("delete.png"))) self.dataPointRemoveButton.setToolTip( "Remove the selected data set from the plot on the right." ) self.dataPointRemoveButton.clicked.connect(self.removeDatapointFromTree) self.dataPointManipulationLayout.addWidget(self.dataPointRemoveButton) self.dataPointExportButton = QPushButton(self) self.dataPointExportButton.setIcon(QIcon(get_resource("export.png"))) self.dataPointExportButton.setToolTip( "Export the selected data set from the left to a csv or png file." ) self.dataPointExportButton.clicked.connect(self.exportDatapointFromTree) self.dataPointManipulationLayout.addWidget(self.dataPointExportButton) self.plotLabel = QLabel("Plots", self) self.plotLabel.setAlignment(Qt.AlignCenter) self.dataPointManipulationLayout.addWidget(self.plotLabel) self.dataPointPlotAddButton = QPushButton(self) self.dataPointPlotAddButton.setIcon(QIcon(get_resource("add.png"))) self.dataPointPlotAddButton.setToolTip("Create a new plot window.") self.dataPointPlotAddButton.clicked.connect(self.addPlotTreeItem) self.dataPointManipulationLayout.addWidget(self.dataPointPlotAddButton) self.dataPointPlotRemoveButton = QPushButton(self) self.dataPointPlotRemoveButton.setIcon(QIcon(get_resource("delete.png"))) self.dataPointPlotRemoveButton.setToolTip( "Delete the selected plot window." ) self.dataPointPlotRemoveButton.clicked.connect(self.removeSelectedPlotTreeItems) self.dataPointManipulationLayout.addWidget(self.dataPointPlotRemoveButton) self.dataPointTreeWidget = QTreeWidget() self.dataPointTreeWidget.setHeaderLabels(["PlotTitle", "DataPoint"]) # self.dataPointTreeWidget.setSelectionMode(QAbstractItemView.MultiSelection) self.dataPointTreeWidget.itemDoubleClicked.connect(self.plot_vector_clicked) self.dataPointTreeWidget.setExpandsOnDoubleClick(0) self.dataPointTreeLayout = QVBoxLayout() self.dataPointTreeWidget.setLayout(self.dataPointTreeLayout) self.dataLayout.addWidget(self.dataPointTreeWidget) self.dataWidget.setLayout(self.dataLayout) self.dataDock.addWidget(self.dataWidget) # actions for simulation control self.actSimulateCurrent = QAction(self) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.setShortcut(QKeySequence("F5")) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actSimulateAll = QAction(self) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.setShortcut(QKeySequence("F6")) self.actSimulateAll.setDisabled(True) self.actSimulateAll.triggered.connect(self.start_regime_execution) # actions for animation control self.actAutoPlay = QAction(self) self.actAutoPlay.setText("&Autoplay Simulation") self.actAutoPlay.setCheckable(True) self.actAutoPlay.setChecked( self._settings.value("control/autoplay_animation") == "True" ) self.actAutoPlay.changed.connect(self.update_autoplay_setting) self.actPlayPause = QAction(self) self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.setShortcut(QKeySequence(Qt.Key_Space)) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.actSlow = QAction(self) self.actSlow.setText("Slowest") self.actSlow.setIcon(QIcon(get_resource("slow.png"))) self.actSlow.setDisabled(False) self.actSlow.triggered.connect(self.set_slowest_playback_speed) self.actFast = QAction(self) self.actFast.setText("Fastest") self.actFast.setIcon(QIcon(get_resource("fast.png"))) self.actFast.setDisabled(False) self.actFast.triggered.connect(self.set_fastest_playback_speed) self.speedControl = QSlider(Qt.Horizontal, self) self.speedControl.setMaximumSize(200, 25) self.speedControl.setTickPosition(QSlider.TicksBothSides) self.speedControl.setDisabled(False) self.speedControl.setMinimum(0) self.speedControl.setMaximum(12) self.speedControl.setValue(6) self.speedControl.setTickInterval(6) self.speedControl.setSingleStep(2) self.speedControl.setPageStep(3) self.speedControl.valueChanged.connect(self.update_playback_speed) self.timeSlider = QSlider(Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = .0 self.playbackGain = 1 self.currentStepSize = .0 self.currentEndTime = .0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actResetCamera = QAction(self) self.actResetCamera.setText("Reset Camera") self.actResetCamera.setIcon(QIcon(get_resource("reset_camera.png"))) self.actResetCamera.setDisabled(True) if available_vis: self.actResetCamera.setEnabled(self.visualizer.can_reset_view) self.actResetCamera.triggered.connect(self.reset_camera_clicked) # postprocessing self.actPostprocessing = QAction(self) self.actPostprocessing.setText("Launch Postprocessor") self.actPostprocessing.setIcon(QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.actPostprocessing.setShortcut(QKeySequence("F7")) self.postprocessor = None # toolbar self.toolbarSim = QToolBar("Simulation") self.toolbarSim.setContextMenuPolicy(Qt.PreventContextMenu) self.toolbarSim.setMovable(False) self.toolbarSim.setIconSize(icon_size) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulateCurrent) self.toolbarSim.addAction(self.actSimulateAll) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSlow) self.toolbarSim.addWidget(self.speedControl) self.toolbarSim.addAction(self.actFast) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.actResetCamera) self.postprocessor = None # log dock self.logBox = QTextEdit(self) self.logBox.setReadOnly(True) self.logBox.setLineWrapMode(QTextEdit.NoWrap) self.logBox.ensureCursorVisible() self.logDock.addWidget(self.logBox) # init logger for logging box self.textLogger = PlainTextLogger(self._settings, logging.INFO) self.textLogger.set_target_cb(self.logBox) logging.getLogger().addHandler(self.textLogger) # menu bar fileMenu = self.menuBar().addMenu("&File") fileMenu.addAction(self.actLoadRegimes) fileMenu.addAction(self.actSave) fileMenu.addAction("&Quit", self.close, QKeySequence(Qt.CTRL + Qt.Key_W)) editMenu = self.menuBar().addMenu("&Edit") editMenu.addAction(self.actDeleteRegimes) self.viewMenu = self.menuBar().addMenu('&View') self.actLoadStandardState = QAction('&Restore Default View') self.viewMenu.addAction(self.actLoadStandardState) self.actLoadStandardState.triggered.connect(self.loadStandardDockState) self.actShowCoords = QAction("&Show Coordinates", self) self.actShowCoords.setCheckable(True) self.actShowCoords.setChecked( self._settings.value("view/show_coordinates") == "True" ) self.viewMenu.addAction(self.actShowCoords) self.actShowCoords.changed.connect(self.update_show_coords_setting) simMenu = self.menuBar().addMenu("&Simulation") simMenu.addAction(self.actSimulateCurrent) simMenu.addAction(self.actSimulateAll) simMenu.addAction(self.actExitOnBatchCompletion) simMenu.addAction(self.actPostprocessing) animMenu = self.menuBar().addMenu("&Animation") animMenu.addAction(self.actPlayPause) animMenu.addAction("&Increase Playback Speed", self.increment_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Plus)) animMenu.addAction("&Decrease Playback Speed", self.decrement_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Minus)) animMenu.addAction("&Reset Playback Speed", self.reset_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_0)) animMenu.addAction(self.actAutoPlay) animMenu.addAction(self.actResetCamera) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("&Online Documentation", self.show_online_docs) helpMenu.addAction("&About", self.show_info) # status bar self.status = QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QLabel("t=0.0") self.statusBar().addPermanentWidget(self.timeLabel) self.coordLabel = QLabel("x=0.0 y=0.0") self.statusBar().addPermanentWidget(self.coordLabel) self._logger.info("Simulation GUI is up and running.") def addPlotTreeItem(self, default=False): text = "plot_{:03d}".format(self.dataPointTreeWidget.topLevelItemCount()) if not default: name, ok = QInputDialog.getText(self, "PlotTitle", "PlotTitle:", text=text) if not (ok and name): return else: name = text similar_items = self.dataPointTreeWidget.findItems(name, Qt.MatchExactly) if similar_items: self._logger.error("Name '{}' already exists".format(name)) return toplevelitem = QTreeWidgetItem() toplevelitem.setText(0, name) self.dataPointTreeWidget.addTopLevelItem(toplevelitem) toplevelitem.setExpanded(1) def removeSelectedPlotTreeItems(self): items = self.dataPointTreeWidget.selectedItems() if not items: self._logger.error("Can't remove plot: no plot selected.") return for item in items: self.removePlotTreeItem(item) def removePlotTreeItem(self, item): # get the top item while item.parent(): item = item.parent() text = "The marked plot '" + item.text(0) + "' will be deleted!" buttonReply = QMessageBox.warning(self, "Plot delete", text, QMessageBox.Ok | QMessageBox.Cancel) if buttonReply == QMessageBox.Ok: openDocks = [dock.title() for dock in self.find_all_plot_docks()] if item.text(0) in openDocks: self.area.docks[item.text(0)].close() self.dataPointTreeWidget.takeTopLevelItem( self.dataPointTreeWidget.indexOfTopLevelItem(item)) def addDatapointToTree(self): if not self.dataPointListWidget.selectedIndexes(): self._logger.error("Can't add data set: no data set selected.") return dataPoints = [] for item in self.dataPointListWidget.selectedItems(): dataPoints.append(item.text()) toplevelItems = self.dataPointTreeWidget.selectedItems() if not toplevelItems: if self.dataPointTreeWidget.topLevelItemCount() < 2: if self.dataPointTreeWidget.topLevelItemCount() < 1: self.addPlotTreeItem(default=True) toplevelItem = self.dataPointTreeWidget.topLevelItem(0) else: self._logger.error("Can't add data set: no plot selected.") return else: toplevelItem = toplevelItems[0] while toplevelItem.parent(): toplevelItem = toplevelItem.parent() topLevelItemList = [] for i in range(toplevelItem.childCount()): topLevelItemList.append(toplevelItem.child(i).text(1)) dock = next((d for d in self.find_all_plot_docks() if d.title() == toplevelItem.text(0)), None) for dataPoint in dataPoints: if dataPoint not in topLevelItemList: child = QTreeWidgetItem() child.setText(1, dataPoint) color = self._get_color(toplevelItem.childCount()) child.setBackground(0, color) toplevelItem.addChild(child) if dock: widget = dock.widgets[0] self.plot_data_vector_member(child, widget) else: self._logger.error("Can't add data set: " "Set '{}' is already present selected plot" "".format(dataPoint)) def exportDatapointFromTree(self): if not self.dataPointListWidget.selectedIndexes(): self._logger.error("Can't export data set: no data set selected.") return dataPoints = {} for item in self.dataPointListWidget.selectedItems(): dataPoints[item.text()] = self._get_data_by_name(item.text()) self.export(dataPoints) def removeDatapointFromTree(self): items = self.dataPointTreeWidget.selectedItems() if not items: self._logger.error("Can't remove data set: no set selected.") return top_item = items[0] while top_item.parent(): top_item = top_item.parent() top_item.takeChild(top_item.indexOfChild(items[0])) for i in range(top_item.childCount()): colorItem = self._get_color(i) top_item.child(i).setBackground(0, colorItem) self._update_plot(top_item) def _get_color(self, idx): sat_idx = idx % len(self.TABLEAU_COLORS) color = QColor(self.TABLEAU_COLORS[sat_idx][1]) return color def _get_pen(self, idx): pen = pg.mkPen(self._get_color(idx), width=2) return pen def plots(self, item): title = item.text(0) # check if a top level item has been clicked if not item.parent(): if title in self.non_plotting_docks: self._logger.error( "Title '{}' not allowed for a plot window since it would" " shadow on of the reserved names".format(title)) return # check if plot has already been opened openDocks = [dock.title() for dock in self.find_all_plot_docks()] if title in openDocks: self._update_plot(item) def plot_vector_clicked(self, item): # check if a top level item has been clicked if item.parent(): return title = item.text(0) if title in self.non_plotting_docks: self._logger.error("Title '{}' not allowed for a plot window since" "it would shadow on of the reserved " "names".format(title)) return # check if plot has already been opened openDocks = [dock.title() for dock in self.find_all_plot_docks()] if title in openDocks: self._update_plot(item) try: self.area.docks[title].raiseDock() except: pass else: self.plot_data_vector(item) def load_last_sim(self, item): sim_name = str(item.text()) try: idx = self.lastSimList.row(item) except ValueError as e: self._logger.error("load_last_sim(): No results called " "'{0}'".format(sim_name)) return False if idx >= len(self._lastSimulations): self._logger.error("load_last_sim(): Invalid index '{}')".format(idx)) return False self._logger.info("restoring simulation '{}'".format(sim_name)) self.currentDataset = self._lastSimulations[idx] if self._lastSimulations[idx]: self._read_results() self._update_data_list() self._update_plots() lsettings = self.currentDataset["modules"] lsettings["clear previous"] = True self.sim.restore_regime(lsettings) self.update_gui() self.setQListItemBold(self.lastSimList, item) self.setQListItemBold(self.regime_list, item) self.statusBar().showMessage( "restored simulation '{}'.".format(sim_name), 1000) def _add_setting(self, setting, value): """ Add a setting, if settings is present, no changes are made. Args: setting(str): Setting to add. value: Value to be set. """ if not self._settings.contains(setting): self._settings.setValue(setting, value) def _init_settings(self): """ Provide initial settings for the config file. """ # path management self._add_setting("path/simulation_results", os.path.join(os.path.curdir, "results", "simulation")) self._add_setting("path/simulation_results", os.path.join(os.path.curdir, "results", "simulation")) self._add_setting("path/postprocessing_results", os.path.join(os.path.curdir, "results", "postprocessing")) self._add_setting("path/metaprocessing_results", os.path.join(os.path.curdir, "results", "metaprocessing")) self._add_setting("path/previous_plot_export", os.path.curdir) self._add_setting("path/previous_plot_format", ".csv") # control flow management self._add_setting("control/autoplay_animation", "False") self._add_setting("control/exit_on_batch_completion", "False") # view management self._add_setting("view/show_coordinates", "True") # log management self._add_setting("log_colors/CRITICAL", "#DC143C") self._add_setting("log_colors/ERROR", "#B22222") self._add_setting("log_colors/WARNING", "#DAA520") self._add_setting("log_colors/INFO", "#101010") self._add_setting("log_colors/DEBUG", "#4682B4") self._add_setting("log_colors/NOTSET", "#000000") self._add_setting("view/show_time_on_export", "False") self._add_setting("view/export_width", "800") self._add_setting("view/export_height", "600") def _write_settings(self): """ Store the application state. """ pass @pyqtSlot() def update_autoplay_setting(self): self._settings.setValue("control/autoplay_animation", str(self.actAutoPlay.isChecked())) @pyqtSlot() def update_show_coords_setting(self): self._settings.setValue("view/show_coordinates", str(self.actShowCoords.isChecked())) @pyqtSlot() def update_exit_on_batch_completion_setting(self, state=None): if state is None: state = self.actExitOnBatchCompletion.isChecked() self._settings.setValue("control/exit_on_batch_completion", str(state)) def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize()
[docs] @pyqtSlot() def play_animation(self): """ play the animation """ self._logger.info("Starting Playback") # if we are at the end, start from the beginning if self.playbackTime == self.currentEndTime: self.timeSlider.setValue(0) self.actPlayPause.setText("Pause Animation") self.actPlayPause.setIcon(QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout)
[docs] @pyqtSlot() def pause_animation(self): """ pause the animation """ self._logger.info("Pausing Playback") self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation)
[docs] def stop_animation(self): """ Stop the animation if it is running and reset the playback time. """ self._logger.info("Stopping Playback") if self.actPlayPause.text() == "Pause Animation": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.timeSlider.setValue(0)
[docs] @pyqtSlot() def start_simulation(self): """ start the simulation and disable start button """ if self._current_regime_index is None: regime_name = "" else: regime_name = str(self.regime_list.item( self._current_regime_index).text()) self.statusLabel.setText("simulating {}".format(regime_name)) self._logger.info("Simulating: {}".format(regime_name)) self.actSimulateCurrent.setIcon(QIcon( get_resource("stop_simulation.png"))) self.actSimulateCurrent.setText("Abort &Simulation") self.actSimulateCurrent.triggered.disconnect(self.start_simulation) self.actSimulateCurrent.triggered.connect(self.stop_simulation) if not self.runningBatch: self.actSimulateAll.setDisabled(True) self.guiProgress = QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit()
@pyqtSlot() def stop_simulation(self): self.stopSimulation.emit()
[docs] def export_simulation_data(self, ok): """ Query the user for a custom name and export the current simulation results. :param ok: unused parameter from QAction.triggered() Signal """ self._save_data()
def _save_data(self, file_path=None): """ Save the current simulation results. If *fie_name* is given, the result will be saved to the specified location, making automated exporting easier. Args: file_path(str): Absolute path of the target file. If `None` the use will be asked for a storage location. """ regime_name = self._regimes[self._current_regime_index]["Name"] if file_path is None: # get default path path = self._settings.value("path/simulation_results") # create canonic file name suggestion = self._simfile_name(regime_name) else: path = os.path.dirname(file_path) suggestion = os.path.basename(file_path) # check if path exists otherwise create it if not os.path.isdir(path): box = QMessageBox() box.setText("Export Folder does not exist yet.") box.setInformativeText("Do you want to create it? \n" "{}".format(os.path.abspath(path))) box.setStandardButtons(QMessageBox.Ok | QMessageBox.No) box.setDefaultButton(QMessageBox.Ok) ret = box.exec_() if ret == QMessageBox.Ok: os.makedirs(path) else: path = os.path.abspath(os.path.curdir) file_path = None # If no path was given, present the default and let the user choose if file_path is None: dialog = QFileDialog(self) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setFileMode(QFileDialog.AnyFile) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Results (*.pmr)") dialog.selectFile(suggestion) if dialog.exec_(): file_path = dialog.selectedFiles()[0] else: self._logger.warning("Export Aborted") return -1 # ask whether this should act as new default path = os.path.abspath(os.path.dirname(file_path)) if path != self._settings.value("path/simulation_results"): box = QMessageBox() box.setText("Use this path as new default?") box.setInformativeText("{}".format(path)) box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.Yes) ret = box.exec_() if ret == QMessageBox.Yes: self._settings.setValue("path/simulation_results", path) self.currentDataset.update({"regime name": regime_name}) with open(file_path, "wb") as f: pickle.dump(self.currentDataset, f, protocol=4) self.statusLabel.setText("results saved to {}".format(file_path)) self._logger.info("results saved to {}".format(file_path)) def _simfile_name(self, regime_name): """ Create a canonical name for a simulation result file """ suggestion = (time.strftime("%Y%m%d-%H%M%S") + "_" + regime_name + ".pmr") return suggestion def load_regime_dialog(self): regime_path = os.path.join(os.curdir) dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setDirectory(regime_path) dialog.setNameFilter("Simulation Regime files (*.sreg)") if dialog.exec_(): file = dialog.selectedFiles()[0] self.load_regimes_from_file(file)
[docs] def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info("loading regime file: {0}".format(self.regime_file_name)) with open(file_name.encode(), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actSimulateAll.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage("loaded {} regimes.".format(len(self._regimes)), 1000) return
def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.info("adding '{}' to regime list".format(reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item))
[docs] @pyqtSlot(QListWidgetItem) def regime_dclicked(self, item): """ Apply the selected regime to the current target. """ success = self._apply_regime_by_idx(self.regime_list.row(item)) self.setQListItemBold(self.regime_list, item, success) self.setQListItemBold(self.lastSimList, item, success) self.dataPointListWidget.clear()
[docs] def apply_regime_by_name(self, regime_name): """ Apply the regime given by `regime_name` und update the regime index. Returns: bool: `True` if successful, `False` if errors occurred. """ # get regime idx try: idx = list(map(itemgetter("Name"), self._regimes)).index(regime_name) except ValueError as e: self._logger.error("apply_regime_by_name(): Error no regime called " "'{0}'".format(regime_name)) return False # apply return self._apply_regime_by_idx(idx)
def _apply_regime_by_idx(self, index=0): """ Apply the given regime. Args: index(int): Index of the regime in the `RegimeList` . Returns: bool: `True` if successful, `False` if errors occurred. """ if index >= len(self._regimes): self._logger.error("Invalid index: '{}'".format(index)) return False reg_name = self._regimes[index]["Name"] self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self._current_regime_name = reg_name ret = self.sim.set_regime(self._regimes[index]) if ret: self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) return ret
[docs] @pyqtSlot() def start_regime_execution(self): """ Simulate all regimes in the regime list. """ self.actSimulateAll.setText("Stop Simulating &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("stop_batch.png"))) self.actSimulateAll.triggered.disconnect(self.start_regime_execution) self.actSimulateAll.triggered.connect(self.stop_regime_execution) self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit()
[docs] def run_next_regime(self): """ Execute the next regime in the regime batch. """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit(True) return suc = self._apply_regime_by_idx(self._current_regime_index + 1) if not suc: self.finishedRegimeBatch.emit(False) return self.start_simulation()
[docs] @pyqtSlot() def stop_regime_execution(self): """ Stop the batch process. """ self.stopSimulation.emit() self.finishedRegimeBatch.emit(False)
def regime_batch_finished(self, status): self.runningBatch = False self.actSimulateAll.setDisabled(False) self.actSave.setDisabled(True) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.triggered.disconnect(self.stop_regime_execution) self.actSimulateAll.triggered.connect(self.start_regime_execution) if status: self.statusLabel.setText("All regimes have been simulated") self._logger.info("All Regimes have been simulated") else: self._logger.error("Batch simulation has been aborted") if self._settings.value("control/exit_on_batch_completion") == "True": self._logger.info("Shutting down SimulationGUI") self.close()
[docs] @pyqtSlot(str, dict, name="new_simulation_data") def new_simulation_data(self, status, data): """ Slot to be called when the simulation interface has completed the current job and new data is available. Args: status (str): Status of the simulation, either - `finished` : Simulation has been finished successfully or - `failed` : Simulation has failed. data (dict): Dictionary, holding the simulation data. """ self._logger.info("Simulation {}".format(status)) self.statusLabel.setText("Simulation {}".format(status)) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.triggered.disconnect(self.stop_simulation) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actPlayPause.setDisabled(False) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedControl.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect(self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.stop_animation() if data: # import new data self.currentDataset = data self._read_results() self._update_data_list() self._update_plots() # add results to history lastSimCount = self.lastSimList.count() lastSimData = {'modules': data['modules'], 'results': data['results'], 'simulation': data['simulation'], 'name': self._current_regime_name, } display_name = "{}:{}".format(lastSimCount, self._current_regime_name) self._lastSimulations.push(lastSimData) new_item = QListWidgetItem(display_name) self.lastSimList.addItem(new_item) self.setQListItemBold(self.lastSimList, new_item) if self._settings.value("control/autoplay_animation") == "True": self.actPlayPause.trigger() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] file_name = self._simfile_name(regime_name) self._save_data(os.path.join( self._settings.value("path/simulation_results"), file_name)) self.regimeFinished.emit() else: self.actSimulateAll.setDisabled(False)
def _read_results(self): state = self.currentDataset["results"]["Solver"] self.interpolator = interp1d(self.currentDataset["results"]["time"], state, axis=0, bounds_error=False, fill_value=(state[0], state[-1])) self.currentStepSize = 1.0 / self.currentDataset["simulation"][ "measure rate"] self.currentEndTime = self.currentDataset["simulation"]["end time"] self.validData = True def increment_playback_speed(self): self.speedControl.setValue(self.speedControl.value() + self.speedControl.singleStep()) def decrement_playback_speed(self): self.speedControl.setValue(self.speedControl.value() - self.speedControl.singleStep()) def reset_playback_speed(self): self.speedControl.setValue((self.speedControl.maximum() - self.speedControl.minimum()) / 2) def set_slowest_playback_speed(self): self.speedControl.setValue(self.speedControl.minimum()) def set_fastest_playback_speed(self): self.speedControl.setValue(self.speedControl.maximum())
[docs] def update_playback_speed(self, val): """ adjust playback time to slider value :param val: """ maximum = self.speedControl.maximum() self.playbackGain = 10 ** (3.0 * (val - maximum / 2) / maximum)
[docs] @pyqtSlot() def increment_playback_time(self): """ go one time step forward in playback """ if self.playbackTime == self.currentEndTime: self.pause_animation() return increment = self.playbackGain * self.playbackTimeout / 1000 self.playbackTime = min(self.currentEndTime, self.playbackTime + increment) pos = int(self.playbackTime / self.currentEndTime * self.timeSliderRange) self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit()
[docs] def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value() / self.timeSliderRange * self.currentEndTime self.playbackTimeChanged.emit() return
[docs] def update_gui(self): """ Updates the graphical user interface to reflect changes of the current display time. This includes: - timestamp - visualisation window - time cursors in diagrams """ if not self.validData: return self.timeLabel.setText("t={0:.3e}".format(self.playbackTime)) # update time cursor in plots self._update_time_cursors() # update state of rendering if self.visualizer: state = self.interpolator(self.playbackTime) self.visualizer.update_scene(state) if isinstance(self.visualizer, MplVisualizer): pass elif isinstance(self.visualizer, VtkVisualizer): self.vtkWidget.GetRenderWindow().Render()
def _update_data_list(self): # self.dataList.clear() self.dataPointListWidget.clear() # TODO lets open and check if possible to plot # TODO create trees with children instead of plain suffixes for module_name, results in self.currentDataset["results"].items(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataPointListWidget.addItem(module_name) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataPointListWidget.addItem( self._build_entry_name(module_name, (col,)) ) elif len(results.shape) == 3: for col in range(results.shape[1]): for der in range(results.shape[2]): self.dataPointListWidget.addItem( self._build_entry_name(module_name, (col, der)) ) def _build_entry_name(self, module_name, idx): """ Construct an identifier for a given entry of a module. Args: module_name (str): name of the module the entry belongs to. idx (tuple): Index of the entry. Returns: str: Identifier to use for display. """ # save the user from defining 1d entries via tuples if len(idx) == 1: m_idx = idx[0] else: m_idx = idx mod_settings = self.currentDataset["modules"] info = mod_settings.get(module_name, {}).get("output_info", None) if info: if m_idx in info: return ".".join([module_name, info[m_idx]["Name"]]) return ".".join([module_name] + [str(i) for i in idx]) def _get_index_from_suffix(self, module_name, suffix): info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) idx = next((i for i in info if info[i]["Name"] == suffix), None) return idx def _get_units(self, entry): """ Return the unit that corresponds to a given entry. If no information is available, None is returned. Args: entry (str): Name of the entry. This can either be "Model.a.b" where a and b are numbers or if information is available "Model.Signal" where signal is the name of that part. Returns: """ args = entry.split(".") module_name = args.pop(0) info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) if info is None: return None if len(args) == 1: try: idx = int(args[0]) except ValueError: idx = next((i for i in info if info[i]["Name"] == args[0]), None) else: idx = (int(a) for a in args) return info[idx]["Unit"] def _update_plot(self, item): # collect data if self.currentDataset is None: return title = item.text(0) t = self.currentDataset["results"]["time"] docks = self.find_all_plot_docks() target = next((d for d in docks if d.title() == title), None) if target is None: return for widget in target.widgets: child_names = [item.child(c_idx).text(1) for c_idx in range(item.childCount())] del_list = [] cnt = 0 for _item in widget.getPlotItem().items: if isinstance(_item, pg.PlotDataItem): if _item.name() in child_names: y_data = self._get_data_by_name(_item.name()) if y_data is not None: _item.setData(x=t, y=y_data) _item.setPen(self._get_pen(cnt)) cnt += 1 else: _item.clear() else: del_list.append(_item) for _item in del_list: widget.getPlotItem().removeItem(_item)
[docs] def plot_data_vector(self, item): """ Creates a plot widget based on the given item. If a plot for this item is already open no new plot is created but the existing one is raised up again. Args: item(Qt.ListItem): Item to plot. """ if self.currentDataset is None: return title = str(item.text(0)) # create plot widget widget = pg.PlotWidget() widget.showGrid(True, True) widget.getPlotItem().getAxis("bottom").setLabel(text="Time", units="s") for idx in range(item.childCount()): self.plot_data_vector_member(item.child(idx), widget) # add a time line time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) coord_item = pg.TextItem(text='', anchor=(0, 1)) widget.getPlotItem().addItem(coord_item, ignoreBounds=True) def _info_wrapper(pos): self.update_coord_info(pos, widget, coord_item) widget.scene().sigMouseMoved.connect(_info_wrapper) # add custom export entries widget.scene().contextMenu = [ QAction("Export as ...", self), ] def _export_wrapper(export_func): def _wrapper(): return export_func(widget.getPlotItem(), title, coord_item, time_line) return _wrapper widget.scene().contextMenu[0].triggered.connect( _export_wrapper(self.export_plot_item)) # create dock container and add it to dock area dock = pg.dockarea.Dock(title, closable=True) dock.addWidget(widget) plotWidgets = self.find_all_plot_docks() if plotWidgets: self.area.addDock(dock, "above", plotWidgets[0]) else: self.area.addDock(dock, "bottom", self.animationDock)
def update_coord_info(self, pos, widget, coord_item): mouse_coords = widget.getPlotItem().vb.mapSceneToView(pos) coord_item.setPos(mouse_coords.x(), mouse_coords.y()) coord_text = "x={:.3e} y={:.3e}".format(mouse_coords.x(), mouse_coords.y()) self.coordLabel.setText(coord_text) show_info = self._settings.value("view/show_coordinates") == "True" if widget.sceneBoundingRect().contains(pos) and show_info: coord_item.setText(coord_text.replace(" ", "\n")) coord_item.show() else: coord_item.hide() def plot_data_vector_member(self, item, widget): idx = item.parent().indexOfChild(item) data_name = item.text(1) t = self.currentDataset["results"]["time"] data = self._get_data_by_name(data_name) if data is None: t = None widget.plot(x=t, y=data, pen=self._get_pen(idx), name=data_name) def find_all_plot_docks(self): list = [] for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue else: list.append(dock) return list def export_plot_item(self, plot_item, name, coord_item, time_item): plot_data = {} for i, c in enumerate(plot_item.curves): if c.getData() is None: continue if len(c.getData()) > 2: self._logger.warning("Can not handle data format of entry" "'{}'!".format(c.name())) continue if "time" not in plot_data: plot_data["time"] = c.getData()[0] c_name = c.name() c_unit = self._get_units(c_name) if c_unit is not None: c_name += " ({})".format(c_unit) plot_data[c_name] = c.getData()[1] self.export(plot_data) def export(self, plot_data): try: exporter = Exporter(data_points=plot_data) except Exception as e: self._logger.error("Can't instantiate exporter! " + str(e)) return last_path = self._settings.value("path/previous_plot_export") last_format = self._settings.value("path/previous_plot_format") export_formats = ["CSV Data (*.csv)", "PNG Image (*.png)"] if last_format == ".png": export_formats[:] = export_formats[::-1] format_str = ";;".join(export_formats) default_file = os.path.join(last_path, "export" + last_format) filename = QFileDialog.getSaveFileName(self, "Export as ...", default_file, format_str) if filename[0]: file, ext = os.path.splitext(filename[0]) self._settings.setValue("path/previous_plot_export", os.path.dirname(file)) if ext == '.csv': exporter.export_csv(filename[0]) self._settings.setValue("path/previous_plot_format", ".csv") elif ext == '.png': exporter.export_png(filename[0]) self._settings.setValue("path/previous_plot_format", ".png") else: self._logger.error("Wrong extension used!") return self._logger.info("Export successful as '{}'.".format(filename[0])) def _get_data_by_name(self, name): tmp = name.split(".") module_name = tmp[0] try: raw_data = self.currentDataset["results"][module_name] except KeyError: return None if len(tmp) == 1: data = np.array(raw_data) elif len(tmp) == 2: try: idx = int(tmp[1]) except ValueError: idx = self._get_index_from_suffix(module_name, tmp[1]) if raw_data.ndim != 2 or raw_data.shape[1] <= idx: return None data = raw_data[:, idx] elif len(tmp) == 3: try: idx = int(tmp[1]) der = int(tmp[2]) except ValueError: return None if raw_data.ndim != 3 or raw_data.shape[1] <= idx \ or raw_data.shape[2] <= der: return None data = raw_data[:, idx, der] else: raise ValueError("Format not supported") return data def _update_time_cursors(self): """ Update the time lines of all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.InfiniteLine): item.setValue(self.playbackTime) def _update_plots(self): """ Update the data in all plot windows """ root = self.dataPointTreeWidget.invisibleRootItem() for i in range(root.childCount()): self._update_plot(root.child(i)) @pyqtSlot(QModelIndex) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0)
[docs] def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info("launching postprocessor") self.statusBar().showMessage("launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show()
[docs] def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render()
def show_info(self): icon_lic = open(get_resource("license.txt"), "r").read() text = "This application was build using PyMoskito ver. {} .<br />" \ "PyMoskito is free software distributed under GPLv3. <br />" \ "It is developed by members of the " \ "<a href=\'https://tu-dresden.de/ing/elektrotechnik/rst'>" \ "Institute of Control Theory</a>" \ " at the <a href=\'https://tu-dresden.de'>" \ "Dresden University of Technology</a>. <br />" \ "".format(pkg_resources.require("PyMoskito")[0].version) \ + "<br />" + icon_lic box = QMessageBox.about(self, "PyMoskito", text) def show_online_docs(self): webbrowser.open("https://pymoskito.readthedocs.org")
[docs] def closeEvent(self, QCloseEvent): self._logger.info("Close Event received, shutting down.") logging.getLogger().removeHandler(self.textLogger) super().closeEvent(QCloseEvent)
def loadStandardDockState(self): for docks in self.find_all_plot_docks(): docks.close() self.area.restoreState(self.standardDockState) def setQListItemBold(self, q_list=None, item=None, state=True): for i in range(q_list.count()): new_font = q_list.item(i).font() if q_list.item(i) == item and state: new_font.setBold(1) else: new_font.setBold(0) q_list.item(i).setFont(new_font) q_list.repaint()