class SimulationGui(QtGui.QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name # TODO add ability to stop an simulation # TODO add ability to stop regime execution runSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal() def __init__(self): # constructor of the base class QtGui.QMainWindow.__init__(self) 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.sim.simulation_finished.connect(self.simulation_finished) self.sim.simulation_failed.connect(self.simulation_failed) self.currentDataset = 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 = QtGui.QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QtGui.QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.vtkDock = pg.dockarea.Dock("Simulation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDocks = [] self.plotDocks.append(pg.dockarea.Dock("Placeholder")) self.plotWidgets = [] self.timeLines = [] # arrange docks self.area.addDock(self.vtkDock, "right") self.area.addDock(self.regimeDock, "left", self.vtkDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDocks[-1], "bottom", self.vtkDock) self.area.addDock(self.logDock, "bottom", self.dataDock) # add widgets to the docks self.propertyDock.addWidget(self.targetView) # vtk window self.vtkLayout = QtGui.QVBoxLayout() self.frame = QtGui.QFrame() self.vtkWidget = QVTKRenderWindowInteractor(self.frame) self.vtkLayout.addWidget(self.vtkWidget) self.frame.setLayout(self.vtkLayout) self.vtkDock.addWidget(self.frame) self.vtk_renderer = vtk.vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer(self.vtk_renderer) # 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 one self._logger.info("loading visualizer '{}'".format( available_vis[0][1])) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self.visualizer = None # regime window self.regime_list = QtGui.QListWidget(self) self.regime_list.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" # data window self.dataList = QtGui.QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulate = QtGui.QAction(self) self.actSimulate.setText("Simulate") self.actSimulate.setIcon(QtGui.QIcon(get_resource("simulate.png"))) self.actSimulate.triggered.connect(self.start_simulation) # actions for animation control self.actPlayPause = QtGui.QAction(self) self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QtGui.QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QtGui.QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.speedDial = QtGui.QDial() self.speedDial.setDisabled(True) self.speedDial.setMinimum(0) self.speedDial.setMaximum(100) self.speedDial.setValue(50) self.speedDial.setSingleStep(1) self.speedDial.resize(24, 24) self.speedDial.valueChanged.connect(self.update_playback_gain) self.timeSlider = QtGui.QSlider(QtCore.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.actSave = QtGui.QAction(self) self.actSave.setText('Save') self.actSave.setIcon(QtGui.QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QtGui.QAction(self) self.actLoadRegimes.setText("load regimes") self.actLoadRegimes.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExecuteRegimes = QtGui.QAction(self) self.actExecuteRegimes.setText("execute all regimes") self.actExecuteRegimes.setIcon( QtGui.QIcon(get_resource("execute_regimes.png"))) self.actExecuteRegimes.setDisabled(True) self.actExecuteRegimes.triggered.connect(self.execute_regimes_clicked) self.actPostprocessing = QtGui.QAction(self) self.actPostprocessing.setText("launch postprocessor") self.actPostprocessing.setIcon( QtGui.QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.act_reset_camera = QtGui.QAction(self) self.act_reset_camera.setText("reset camera") self.act_reset_camera.setIcon( QtGui.QIcon(get_resource("reset_camera.png"))) self.act_reset_camera.setDisabled(not self.visualizer.can_reset_view) self.act_reset_camera.triggered.connect(self.reset_camera_clicked) # toolbar for control self.toolbarSim = QtGui.QToolBar("Simulation") self.toolbarSim.setIconSize(QtCore.QSize(32, 32)) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulate) self.toolbarSim.addAction(self.actExecuteRegimes) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.speedDial) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.act_reset_camera) self.postprocessor = None # regime management self.runningBatch = False self._current_regime_index = 0 self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # log dock self.logBox = QPlainTextEditLogger(self) self.logBox.setLevel(logging.INFO) formatter = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S") self.logBox.setFormatter(formatter) self.log_filter = PostFilter(invert=True) self.logBox.addFilter(self.log_filter) logging.getLogger().addHandler(self.logBox) self.logDock.addWidget(self.logBox.widget) # status bar self.status = QtGui.QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QtGui.QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QtGui.QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) # shortcuts self.delShort = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Delete), self.regime_list) self.delShort.activated.connect(self.remove_regime_items) self.shortOpenRegime = QtGui.QShortcut(QtGui.QKeySequence.Open, self) self.shortOpenRegime.activated.connect(self.load_regime_dialog) self.shortSaveResult = QtGui.QShortcut(QtGui.QKeySequence.Save, self) self.shortSaveResult.activated.connect(self.export_simulation_data) self.shortSaveResult.setEnabled(False) self.shortSpeedUp = QtGui.QShortcut(QtGui.QKeySequence.ZoomIn, self) self.shortSpeedUp.activated.connect(self.increment_playback_speed) self.shortSpeedDown = QtGui.QShortcut(QtGui.QKeySequence.ZoomOut, self) self.shortSpeedDown.activated.connect(self.decrement_playback_speed) self.shortSpeedReset = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_0), self) self.shortSpeedReset.activated.connect(self.reset_playback_speed) self.shortRunSimulation = QtGui.QShortcut(QtGui.QKeySequence('F5'), self) self.shortRunSimulation.activated.connect(self.start_simulation) self.shortRunRegimeBatch = QtGui.QShortcut(QtGui.QKeySequence('F6'), self) self.shortRunRegimeBatch.activated.connect( self.execute_regimes_clicked) self.shortRunPostprocessing = QtGui.QShortcut(QtGui.QKeySequence('F7'), self) self.shortRunPostprocessing.activated.connect( self.postprocessing_clicked) self.shortPlayPause = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Space), self) self.shortPlayPause.activated.connect(self.play_animation) self.shortPlayPause.setEnabled(False) self.postprocessor = None self._logger.info("Simulation GUI is up and running.") def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() def play_animation(self): """ play the animation """ # self.statusLabel.setText('playing animation') self.actPlayPause.setText("Pause") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.shortPlayPause.activated.disconnect(self.play_animation) self.shortPlayPause.activated.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) def pause_animation(self): """ pause the animation """ # self.statusLabel.setText('pausing animation') self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) def stop_animation(self): """ pause the animation """ # self.statusLabel.setText('stopping animation') if self.actPlayPause.text() == "Pause": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) self.timeSlider.setValue(0) def start_simulation(self): """ start the simulation and disable start button """ regime_name = str( self.regime_list.item(self._current_regime_index).text()) self.statusLabel.setText(u"simulating {}".format(regime_name)) self._logger.info(u"Simulating: {}".format(regime_name)) self.actSimulate.setDisabled(True) self.shortRunSimulation.setEnabled(False) self.shortRunRegimeBatch.setEnabled(False) self.actExecuteRegimes.setDisabled(True) self.guiProgress = QtGui.QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() def export_simulation_data(self, ok): """ query the user for a custome name and export the current simulation results :param ok: unused parameter from QAction.triggered() Signal """ res, ok = QtGui.QInputDialog.getText( self, u"PyMoskito", u"Please specify regime a name", QtGui.QLineEdit.Normal, self._regimes[self._current_regime_index]["Name"]) if not ok: return name = unicode(res.toUtf8(), encoding="utf-8") if not name: self._logger.warning(u"empty regime name specified!") self._save_data(name) def _save_data(self, name): """ save current data-set :param name: name of the file """ self.currentDataset.update({"regime name": name}) path = os.path.join(os.path.pardir, "results", "simulation", self.regime_file_name) # check for path existence if not os.path.isdir(path): os.makedirs(path) # pmr - PyMoskito Result file_name = os.path.join( path, time.strftime("%Y%m%d-%H%M%S") + "_" + name + ".pmr") with open(file_name.encode("utf-8"), "wb") as f: cPickle.dump(self.currentDataset, f, protocol=2) self.statusLabel.setText(u"results saved to {}".format(file_name)) self._logger.info(u"results saved to {}".format(file_name)) def load_regime_dialog(self): regime_path = os.path.join("../regimes/") file_name = unicode(QtGui.QFileDialog.getOpenFileName( self, "Open Regime File", regime_path, "Simulation Regime files (*.sreg)").toUtf8(), encoding="utf-8") if not file_name: return self.load_regimes_from_file(file_name) 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(u"loading regime file: {0}".format( self.regime_file_name)) with open(file_name.encode("utf-8"), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actExecuteRegimes.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage( u"loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("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)) def regime_dclicked(self, item): """ applies the selected regime to the current target :param item: """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ :param regime_name: :return: """ # get regime idx try: idx = map(itemgetter("Name"), self._regimes).index(regime_name) except ValueError as e: self._logger.info( "apply_regime_by_name(): Error no regime called {0}".format( regime_name)) return # apply self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self.sim.set_regime(self._regimes[index]) def execute_regimes_clicked(self): """ execute all regimes in the current list """ self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ executes the next regime """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit() return self._apply_regime_by_idx(self._current_regime_index + 1) self.start_simulation() def regime_batch_finished(self): self.runningBatch = False self.actExecuteRegimes.setDisabled(False) # self._current_regime_index = 0 self.statusLabel.setText("All regimes have been simulated!") self.actSave.setDisabled(True) def simulation_finished(self, data): """ main hook to be called by the simulation interface if integration is finished integration finished, enable play button and update plots :param data: dict with simulation data """ self._logger.info(u"simulation finished") self.statusLabel.setText(u"simulation finished.") self.actSimulate.setDisabled(False) self.shortRunSimulation.setEnabled(True) self.shortRunRegimeBatch.setEnabled(True) self.actPlayPause.setDisabled(False) self.shortPlayPause.setEnabled(True) self.shortSaveResult.setEnabled(True) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedDial.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect( self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.timeSlider.triggerAction(QtGui.QAbstractSlider.SliderToMinimum) self.currentDataset = data self._read_results() self._update_data_list() self._update_plots() self.stop_animation() # self.playAnimation() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] self._save_data(regime_name) self.regimeFinished.emit() else: self.actExecuteRegimes.setDisabled(False) def simulation_failed(self, data): """ integration failed, enable play button and update plots :param data: """ self.statusLabel.setText(u"simulation failed!") self.simulation_finished(data) def _read_results(self): self.currentStepSize = 1.0 / self.currentDataset["results"][ "Simulation"]['measure rate'] self.currentEndTime = self.currentDataset["results"]["Simulation"][ "end time"] self.validData = True def add_plot_to_dock(self, plot_widget): self.d3.addWidget(plot_widget) def increment_playback_speed(self): self.speedDial.setValue(self.speedDial.value() + self.speedDial.singleStep()) def decrement_playback_speed(self): self.speedDial.setValue(self.speedDial.value() - self.speedDial.singleStep()) def reset_playback_speed(self): self.speedDial.setValue(self.speedDial.range() / 2) def increment_playback_time(self): """ go one time step forward in playback """ increment = self.playbackGain * self.playbackTimeout / 1000 if self.playbackTime + increment <= self.currentEndTime: self.playbackTime += increment pos = self.playbackTime / self.currentEndTime * self.timeSliderRange self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() else: self.pause_animation() return def update_playback_gain(self, val): """ adjust playback time to slider value :param val: """ self.playbackGain = 10**(5.0 * (val - self.speedDial.maximum() / 2) / self.speedDial.maximum()) def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value( ) / self.timeSliderRange * self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText(u"current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursor() # update state of rendering if self.visualizer: state = self.interpolate(self.currentDataset["results"]["Solver"]) self.visualizer.update_scene(state) self.vtkWidget.GetRenderWindow().Render() def interpolate(self, data): """ find corresponding item in data-set that fits the current playback time :param data: data-set in which the correct datum has to be found :return: datum fitting the current playback time """ idx = next(( index for index, val in enumerate(self.currentDataset["results"]["time"]) if val >= self.playbackTime), None) if idx is None: self._logger.info( u"interpolate(): Error no entry found for t={0}".format( self.playbackTime)) return None else: if len(data.shape) == 1: return data[idx] elif len(data.shape) == 2: return data[idx, :] else: self._logger.info( u"interpolate(): Error Dimension {0} not understood.". format(data.shape)) return None def _update_data_list(self): self.dataList.clear() for module, results in self.currentDataset["results"].iteritems(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, "{0}".format(module)) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) elif len(results.shape) == 3: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) def create_plot(self, item): """ creates a plot widget corresponding to the ListItem :param item: ListItem """ title = str(item.text()) data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] dock = pg.dockarea.Dock(title) self.area.addDock(dock, "above", self.plotDocks[-1]) widget = pg.PlotWidget(title=title) widget.plot(x=t, y=data) time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # enable grid widget.showGrid(True, True) dock.addWidget(widget) self.plotDocks.append(dock) self.plotWidgets.append(widget) self.timeLines.append(time_line) def _get_data_by_name(self, name): tmp = name.split(".") if len(tmp) == 1: data = self.currentDataset["results"][tmp[0]] # if the data-set contains 1d array -> convert to float data = [float(x) for x in data] elif len(tmp) == 2: if len(self.currentDataset["results"][tmp[0]].shape) == 2: data = self.currentDataset["results"][tmp[0]][:, tmp[1]] if len(self.currentDataset["results"][tmp[0]].shape ) == 3: # data-set has the structure [ [[1]], [[2]] ] data = self.currentDataset['results'][tmp[0]][..., tmp[1], 0] return data def _update_time_cursor(self): """ updates the time lines of all plot windows """ for line in self.timeLines: line.setValue(self.playbackTime) def _update_plots(self): """ plot the fresh simulation data """ for dock in self.plotDocks: for widget in dock.widgets: if not self.dataList.findItems(dock.name(), QtCore.Qt.MatchExactly): # no data for this plot -> reset it widget.getPlotItem().clear() # TODO remove tab from dock and del instance else: widget.getPlotItem().clear() x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) widget.getPlotItem().plot(x=x_data, y=y_data) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info(u"launching postprocessor") self.statusBar().showMessage(u"launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render()
class SimulationGui(QtGui.QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name # TODO add ability to stop an simulation # TODO add ability to stop regime execution runSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal() def __init__(self): # constructor of the base class QtGui.QMainWindow.__init__(self) 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.sim.simulation_finished.connect(self.simulation_finished) self.sim.simulation_failed.connect(self.simulation_failed) self.currentDataset = 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 = QtGui.QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QtGui.QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.vtkDock = pg.dockarea.Dock("Simulation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDocks = [] self.plotDocks.append(pg.dockarea.Dock("Placeholder")) self.plotWidgets = [] self.timeLines = [] # arrange docks self.area.addDock(self.vtkDock, "right") self.area.addDock(self.regimeDock, "left", self.vtkDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDocks[-1], "bottom", self.vtkDock) self.area.addDock(self.logDock, "bottom", self.dataDock) # add widgets to the docks self.propertyDock.addWidget(self.targetView) # vtk window self.vtkLayout = QtGui.QVBoxLayout() self.frame = QtGui.QFrame() self.vtkWidget = QVTKRenderWindowInteractor(self.frame) self.vtkLayout.addWidget(self.vtkWidget) self.frame.setLayout(self.vtkLayout) self.vtkDock.addWidget(self.frame) self.vtk_renderer = vtk.vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer(self.vtk_renderer) # 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 one self._logger.info("loading visualizer '{}'".format(available_vis[0][1])) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self.visualizer = None # regime window self.regime_list = QtGui.QListWidget(self) self.regime_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" # data window self.dataList = QtGui.QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulate = QtGui.QAction(self) self.actSimulate.setText("Simulate") self.actSimulate.setIcon(QtGui.QIcon(get_resource("simulate.png"))) self.actSimulate.triggered.connect(self.start_simulation) # actions for animation control self.actPlayPause = QtGui.QAction(self) self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QtGui.QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QtGui.QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.speedDial = QtGui.QDial() self.speedDial.setDisabled(True) self.speedDial.setMinimum(0) self.speedDial.setMaximum(100) self.speedDial.setValue(50) self.speedDial.setSingleStep(1) self.speedDial.resize(24, 24) self.speedDial.valueChanged.connect(self.update_playback_gain) self.timeSlider = QtGui.QSlider(QtCore.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.actSave = QtGui.QAction(self) self.actSave.setText('Save') self.actSave.setIcon(QtGui.QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QtGui.QAction(self) self.actLoadRegimes.setText("load regimes") self.actLoadRegimes.setIcon(QtGui.QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExecuteRegimes = QtGui.QAction(self) self.actExecuteRegimes.setText("execute all regimes") self.actExecuteRegimes.setIcon(QtGui.QIcon(get_resource("execute_regimes.png"))) self.actExecuteRegimes.setDisabled(True) self.actExecuteRegimes.triggered.connect(self.execute_regimes_clicked) self.actPostprocessing = QtGui.QAction(self) self.actPostprocessing.setText("launch postprocessor") self.actPostprocessing.setIcon(QtGui.QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.act_reset_camera = QtGui.QAction(self) self.act_reset_camera.setText("reset camera") self.act_reset_camera.setIcon(QtGui.QIcon(get_resource("reset_camera.png"))) self.act_reset_camera.setDisabled(not self.visualizer.can_reset_view) self.act_reset_camera.triggered.connect(self.reset_camera_clicked) # toolbar for control self.toolbarSim = QtGui.QToolBar("Simulation") self.toolbarSim.setIconSize(QtCore.QSize(32, 32)) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulate) self.toolbarSim.addAction(self.actExecuteRegimes) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.speedDial) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.act_reset_camera) self.postprocessor = None # regime management self.runningBatch = False self._current_regime_index = 0 self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # log dock self.logBox = QPlainTextEditLogger(self) self.logBox.setLevel(logging.INFO) formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S") self.logBox.setFormatter(formatter) self.log_filter = PostFilter(invert=True) self.logBox.addFilter(self.log_filter) logging.getLogger().addHandler(self.logBox) self.logDock.addWidget(self.logBox.widget) # status bar self.status = QtGui.QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QtGui.QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QtGui.QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) # shortcuts self.delShort = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Delete), self.regime_list) self.delShort.activated.connect(self.remove_regime_items) self.shortOpenRegime = QtGui.QShortcut(QtGui.QKeySequence.Open, self) self.shortOpenRegime.activated.connect(self.load_regime_dialog) self.shortSaveResult = QtGui.QShortcut(QtGui.QKeySequence.Save, self) self.shortSaveResult.activated.connect(self.export_simulation_data) self.shortSaveResult.setEnabled(False) self.shortSpeedUp = QtGui.QShortcut(QtGui.QKeySequence.ZoomIn, self) self.shortSpeedUp.activated.connect(self.increment_playback_speed) self.shortSpeedDown = QtGui.QShortcut(QtGui.QKeySequence.ZoomOut, self) self.shortSpeedDown.activated.connect(self.decrement_playback_speed) self.shortSpeedReset = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_0), self) self.shortSpeedReset.activated.connect(self.reset_playback_speed) self.shortRunSimulation = QtGui.QShortcut(QtGui.QKeySequence('F5'), self) self.shortRunSimulation.activated.connect(self.start_simulation) self.shortRunRegimeBatch = QtGui.QShortcut(QtGui.QKeySequence('F6'), self) self.shortRunRegimeBatch.activated.connect(self.execute_regimes_clicked) self.shortRunPostprocessing = QtGui.QShortcut(QtGui.QKeySequence('F7'), self) self.shortRunPostprocessing.activated.connect(self.postprocessing_clicked) self.shortPlayPause = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Space), self) self.shortPlayPause.activated.connect(self.play_animation) self.shortPlayPause.setEnabled(False) self.postprocessor = None self._logger.info("Simulation GUI is up and running.") def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() def play_animation(self): """ play the animation """ # self.statusLabel.setText('playing animation') self.actPlayPause.setText("Pause") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.shortPlayPause.activated.disconnect(self.play_animation) self.shortPlayPause.activated.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) def pause_animation(self): """ pause the animation """ # self.statusLabel.setText('pausing animation') self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) def stop_animation(self): """ pause the animation """ # self.statusLabel.setText('stopping animation') if self.actPlayPause.text() == "Pause": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play") self.actPlayPause.setIcon(QtGui.QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.shortPlayPause.activated.disconnect(self.pause_animation) self.shortPlayPause.activated.connect(self.play_animation) self.timeSlider.setValue(0) def start_simulation(self): """ start the simulation and disable start button """ regime_name = str(self.regime_list.item(self._current_regime_index).text()) self.statusLabel.setText(u"simulating {}".format(regime_name)) self._logger.info(u"Simulating: {}".format(regime_name)) self.actSimulate.setDisabled(True) self.shortRunSimulation.setEnabled(False) self.shortRunRegimeBatch.setEnabled(False) self.actExecuteRegimes.setDisabled(True) self.guiProgress = QtGui.QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() def export_simulation_data(self, ok): """ query the user for a custome name and export the current simulation results :param ok: unused parameter from QAction.triggered() Signal """ res, ok = QtGui.QInputDialog.getText(self, u"PyMoskito", u"Please specify regime a name", QtGui.QLineEdit.Normal, self._regimes[self._current_regime_index]["Name"]) if not ok: return name = unicode(res.toUtf8(), encoding="utf-8") if not name: self._logger.warning(u"empty regime name specified!") self._save_data(name) def _save_data(self, name): """ save current data-set :param name: name of the file """ self.currentDataset.update({"regime name": name}) path = os.path.join(os.path.pardir, "results", "simulation", self.regime_file_name) # check for path existence if not os.path.isdir(path): os.makedirs(path) # pmr - PyMoskito Result file_name = os.path.join(path, time.strftime("%Y%m%d-%H%M%S") + "_" + name + ".pmr") with open(file_name.encode("utf-8"), "wb") as f: cPickle.dump(self.currentDataset, f, protocol=2) self.statusLabel.setText(u"results saved to {}".format(file_name)) self._logger.info(u"results saved to {}".format(file_name)) def load_regime_dialog(self): regime_path = os.path.join("../regimes/") file_name = unicode(QtGui.QFileDialog.getOpenFileName(self, "Open Regime File", regime_path, "Simulation Regime files (*.sreg)").toUtf8(), encoding="utf-8") if not file_name: return self.load_regimes_from_file(file_name) 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(u"loading regime file: {0}".format(self.regime_file_name)) with open(file_name.encode("utf-8"), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actExecuteRegimes.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage(u"loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("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)) def regime_dclicked(self, item): """ applies the selected regime to the current target :param item: """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ :param regime_name: :return: """ # get regime idx try: idx = map(itemgetter("Name"), self._regimes).index(regime_name) except ValueError as e: self._logger.info("apply_regime_by_name(): Error no regime called {0}".format(regime_name)) return # apply self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self.sim.set_regime(self._regimes[index]) def execute_regimes_clicked(self): """ execute all regimes in the current list """ self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ executes the next regime """ # are we finished? if self._current_regime_index == len(self._regimes)-1: self.finishedRegimeBatch.emit() return self._apply_regime_by_idx(self._current_regime_index + 1) self.start_simulation() def regime_batch_finished(self): self.runningBatch = False self.actExecuteRegimes.setDisabled(False) # self._current_regime_index = 0 self.statusLabel.setText("All regimes have been simulated!") self.actSave.setDisabled(True) def simulation_finished(self, data): """ main hook to be called by the simulation interface if integration is finished integration finished, enable play button and update plots :param data: dict with simulation data """ self._logger.info(u"simulation finished") self.statusLabel.setText(u"simulation finished.") self.actSimulate.setDisabled(False) self.shortRunSimulation.setEnabled(True) self.shortRunRegimeBatch.setEnabled(True) self.actPlayPause.setDisabled(False) self.shortPlayPause.setEnabled(True) self.shortSaveResult.setEnabled(True) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedDial.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect(self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.timeSlider.triggerAction(QtGui.QAbstractSlider.SliderToMinimum) self.currentDataset = data self._read_results() self._update_data_list() self._update_plots() self.stop_animation() # self.playAnimation() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] self._save_data(regime_name) self.regimeFinished.emit() else: self.actExecuteRegimes.setDisabled(False) def simulation_failed(self, data): """ integration failed, enable play button and update plots :param data: """ self.statusLabel.setText(u"simulation failed!") self.simulation_finished(data) def _read_results(self): self.currentStepSize = 1.0/self.currentDataset["results"]["Simulation"]['measure rate'] self.currentEndTime = self.currentDataset["results"]["Simulation"]["end time"] self.validData = True def add_plot_to_dock(self, plot_widget): self.d3.addWidget(plot_widget) def increment_playback_speed(self): self.speedDial.setValue(self.speedDial.value() + self.speedDial.singleStep()) def decrement_playback_speed(self): self.speedDial.setValue(self.speedDial.value() - self.speedDial.singleStep()) def reset_playback_speed(self): self.speedDial.setValue(self.speedDial.range()/2) def increment_playback_time(self): """ go one time step forward in playback """ increment = self.playbackGain * self.playbackTimeout / 1000 if self.playbackTime + increment <= self.currentEndTime: self.playbackTime += increment pos = self.playbackTime / self.currentEndTime * self.timeSliderRange self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() else: self.pause_animation() return def update_playback_gain(self, val): """ adjust playback time to slider value :param val: """ self.playbackGain = 10**(5.0*(val - self.speedDial.maximum()/2)/self.speedDial.maximum()) def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value()/self.timeSliderRange*self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText(u"current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursor() # update state of rendering if self.visualizer: state = self.interpolate(self.currentDataset["results"]["Solver"]) self.visualizer.update_scene(state) self.vtkWidget.GetRenderWindow().Render() def interpolate(self, data): """ find corresponding item in data-set that fits the current playback time :param data: data-set in which the correct datum has to be found :return: datum fitting the current playback time """ idx = next((index for index, val in enumerate(self.currentDataset["results"]["time"]) if val >= self.playbackTime), None) if idx is None: self._logger.info(u"interpolate(): Error no entry found for t={0}".format(self.playbackTime)) return None else: if len(data.shape) == 1: return data[idx] elif len(data.shape) == 2: return data[idx, :] else: self._logger.info(u"interpolate(): Error Dimension {0} not understood.".format(data.shape)) return None def _update_data_list(self): self.dataList.clear() for module, results in self.currentDataset["results"].iteritems(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, "{0}".format(module)) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) elif len(results.shape) == 3: for col in range(results.shape[1]): self.dataList.insertItem(0, "{0}.{1}".format(module, col)) def create_plot(self, item): """ creates a plot widget corresponding to the ListItem :param item: ListItem """ title = str(item.text()) data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] dock = pg.dockarea.Dock(title) self.area.addDock(dock, "above", self.plotDocks[-1]) widget = pg.PlotWidget(title=title) widget.plot(x=t, y=data) time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # enable grid widget.showGrid(True, True) dock.addWidget(widget) self.plotDocks.append(dock) self.plotWidgets.append(widget) self.timeLines.append(time_line) def _get_data_by_name(self, name): tmp = name.split(".") if len(tmp) == 1: data = self.currentDataset["results"][tmp[0]] # if the data-set contains 1d array -> convert to float data = [float(x) for x in data] elif len(tmp) == 2: if len(self.currentDataset["results"][tmp[0]].shape) == 2: data = self.currentDataset["results"][tmp[0]][:, tmp[1]] if len(self.currentDataset["results"][tmp[0]].shape) == 3: # data-set has the structure [ [[1]], [[2]] ] data = self.currentDataset['results'][tmp[0]][..., tmp[1], 0] return data def _update_time_cursor(self): """ updates the time lines of all plot windows """ for line in self.timeLines: line.setValue(self.playbackTime) def _update_plots(self): """ plot the fresh simulation data """ for dock in self.plotDocks: for widget in dock.widgets: if not self.dataList.findItems(dock.name(), QtCore.Qt.MatchExactly): # no data for this plot -> reset it widget.getPlotItem().clear() # TODO remove tab from dock and del instance else: widget.getPlotItem().clear() x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) widget.getPlotItem().plot(x=x_data, y=y_data) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info(u"launching postprocessor") self.statusBar().showMessage(u"launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render()