diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/frontEnd/Application.py | 100 | ||||
-rwxr-xr-x | src/frontEnd/DockArea.py | 15 | ||||
-rw-r--r-- | src/ngspiceSimulation/NgspiceWidget.py | 156 |
3 files changed, 124 insertions, 147 deletions
diff --git a/src/frontEnd/Application.py b/src/frontEnd/Application.py index ae29c657..790bf779 100644 --- a/src/frontEnd/Application.py +++ b/src/frontEnd/Application.py @@ -10,15 +10,17 @@ # BUGS: --- # NOTES: --- # AUTHOR: Fahim Khan, fahim.elex@gmail.com -# MAINTAINED: Rahul Paknikar, rahulp@cse.iitb.ac.in +# MAINTAINED: Rahul Paknikar, rahulp@iitb.ac.in # Sumanto Kar, sumantokar@iitb.ac.in # Pranav P, pranavsdreams@gmail.com # ORGANIZATION: eSim Team at FOSSEE, IIT Bombay # CREATED: Tuesday 24 February 2015 -# REVISION: Thursday 01 June 2023 +# REVISION: Wednesday 07 June 2023 # ========================================================================= import os +import sys +import shutil import traceback if os.name == 'nt': @@ -29,19 +31,16 @@ else: init_path = '../../' from PyQt5 import QtGui, QtCore, QtWidgets +from PyQt5.Qt import QSize from configuration.Appconfig import Appconfig +from frontEnd import ProjectExplorer +from frontEnd import Workspace +from frontEnd import DockArea from projManagement.openProject import OpenProjectInfo from projManagement.newProject import NewProjectInfo from projManagement.Kicad import Kicad from projManagement.Validation import Validation from projManagement import Worker -from frontEnd import ProjectExplorer -from frontEnd import Workspace -from frontEnd import DockArea -from PyQt5.Qt import QSize -import shutil -import sys -import psutil # Its our main window of application. @@ -49,6 +48,7 @@ import psutil class Application(QtWidgets.QMainWindow): """This class initializes all objects used in this file.""" global project_name + simulationEndSignal = QtCore.pyqtSignal(QtCore.QProcess.ExitStatus, int) def __init__(self, *args): """Initialize main Application window.""" @@ -59,6 +59,9 @@ class Application(QtWidgets.QMainWindow): # Flag for mode of operation. Default is set to offline mode. self.online_flag = False + # Set slot for simulation end signal to plot simulation data + self.simulationEndSignal.connect(self.plotSimulationData) + # Creating require Object self.obj_workspace = Workspace.Workspace() self.obj_Mainview = MainView() @@ -534,35 +537,17 @@ class Application(QtWidgets.QMainWindow): print("Current Project is : ", self.obj_appconfig.current_project) self.obj_Mainview.obj_dockarea.usermanual() - def checkIfProcessRunning(self, processName): - ''' - Check if there is any running process - that contains the given name processName. - ''' - # Iterate over the all the running process - for proc in psutil.process_iter(): - try: - # Check if process name contains the given name string. - if processName.lower() in proc.name().lower(): - return True - except (psutil.NoSuchProcess, - psutil.AccessDenied, psutil.ZombieProcess): - pass - return False - - def checkNgspiceProcessFinished(self, exitCode): - """Checks whether the QProcess that runs ngspice - finished successfully and displays the plotter - where graphs can be plotted. - :param exitCode: The exit status of the ngspice QProcess - :type exitCode: int + @QtCore.pyqtSlot(QtCore.QProcess.ExitStatus, int) + def plotSimulationData(self, exitCode, exitStatus): + """Enables interaction for new simulation and + displays the plotter dock where graphs can be plotted. """ self.ngspice.setEnabled(True) self.conversion.setEnabled(True) self.closeproj.setEnabled(True) self.wrkspce.setEnabled(True) - if exitCode == 0: + if exitStatus == QtCore.QProcess.NormalExit and exitCode == 0: try: self.obj_Mainview.obj_dockarea.plottingEditor() except Exception as e: @@ -570,61 +555,42 @@ class Application(QtWidgets.QMainWindow): self.msg.setModal(True) self.msg.setWindowTitle("Error Message") self.msg.showMessage( - 'Error while opening python plotting Editor.' - ' Please look at console for more details.' + 'Data could not be plotted. Please try again.' ) self.msg.exec_() print("Exception Message:", str(e), traceback.format_exc()) self.obj_appconfig.print_error('Exception Message : ' + str(e)) - def startSimulation(self, process, function): - """This function is used to disable buttons related to simulation - during the ngspice simulation and to connect the - `self.checkNgspiceProcessFinished` function to finished signal if not - already connected. - :param process: The QProcess that runs the simulation - :type process: :class:`QtCore.QProcess` - :param function: Used to call the finishSimulation function in - :class:`NgspiceWidget.NgspiceWidget` class - :type function: function""" - self.ngspice.setEnabled(False) - self.conversion.setEnabled(False) - self.closeproj.setEnabled(False) - self.wrkspce.setEnabled(False) - - if process.isFinishConnected is False: - process.isFinishConnected = True - -# Calls the finished connect exactly once. - process.finished.connect( - lambda exitCode, exitStatus: - function(exitCode, exitStatus, - self.checkNgspiceProcessFinished) - ) - def open_ngspice(self): """This Function execute ngspice on current project.""" - self.projDir = self.obj_appconfig.current_project["ProjectName"] + projDir = self.obj_appconfig.current_project["ProjectName"] - if self.projDir is not None: + if projDir is not None: + projName = os.path.basename(projDir) + ngspiceNetlist = os.path.join(projDir, projName + ".cir.out") - # Edited by Sumanto Kar 25/08/2021 - if self.obj_Mainview.obj_dockarea.ngspiceEditor( - self.projDir, self.startSimulation) is False: + if not os.path.isfile(ngspiceNetlist): print( "Netlist file (*.cir.out) not found." ) - self.msg = QtWidgets.QErrorMessage() self.msg.setModal(True) self.msg.setWindowTitle("Error Message") self.msg.showMessage( - 'Netlist file (*.cir.out) not found.' + 'Netlist (*.cir.out) not found.' ) self.msg.exec_() return + self.obj_Mainview.obj_dockarea.ngspiceEditor( + projName, ngspiceNetlist, self.simulationEndSignal) + + self.ngspice.setEnabled(False) + self.conversion.setEnabled(False) + self.closeproj.setEnabled(False) + self.wrkspce.setEnabled(False) + else: self.msg = QtWidgets.QErrorMessage() self.msg.setModal(True) @@ -726,7 +692,7 @@ class Application(QtWidgets.QMainWindow): # Creating a command for Ngspice to Modelica converter self.cmd1 = " python3 ../ngspicetoModelica/NgspicetoModelica.py "\ - +self.ngspiceNetlist + + self.ngspiceNetlist self.obj_workThread1 = Worker.WorkerThread(self.cmd1) self.obj_workThread1.start() if self.obj_validation.validateTool("OMEdit"): diff --git a/src/frontEnd/DockArea.py b/src/frontEnd/DockArea.py index dbf43cf4..7037dcfd 100755 --- a/src/frontEnd/DockArea.py +++ b/src/frontEnd/DockArea.py @@ -121,28 +121,19 @@ class DockArea(QtWidgets.QMainWindow): ) count = count + 1 - def ngspiceEditor(self, projDir, startSimulation): + def ngspiceEditor(self, projName, netlist, simEndSignal): """ This function creates widget for Ngspice window.""" - self.projDir = projDir - self.projName = os.path.basename(self.projDir) - dockName = f'Simulation-{self.projName}-' - self.ngspiceNetlist = os.path.join( - self.projDir, self.projName + ".cir.out") - - # Edited by Sumanto Kar 25/08/2021 - if os.path.isfile(self.ngspiceNetlist) is False: - return False - global count self.ngspiceWidget = QtWidgets.QWidget() self.ngspiceLayout = QtWidgets.QVBoxLayout() self.ngspiceLayout.addWidget( - NgspiceWidget(self.ngspiceNetlist, startSimulation) + NgspiceWidget(netlist, simEndSignal) ) # Adding to main Layout self.ngspiceWidget.setLayout(self.ngspiceLayout) + dockName = f'Simulation-{projName}-' dock[dockName + str(count) ] = QtWidgets.QDockWidget(dockName + str(count)) diff --git a/src/ngspiceSimulation/NgspiceWidget.py b/src/ngspiceSimulation/NgspiceWidget.py index d0ec7337..e940d995 100644 --- a/src/ngspiceSimulation/NgspiceWidget.py +++ b/src/ngspiceSimulation/NgspiceWidget.py @@ -1,13 +1,13 @@ +import os from PyQt5 import QtWidgets, QtCore from configuration.Appconfig import Appconfig from frontEnd import TerminalUi -import os # This Class creates NgSpice Window class NgspiceWidget(QtWidgets.QWidget): - def __init__(self, netlist, startSimulation): + def __init__(self, netlist, simEndSignal): """ - Creates constructor for NgspiceWidget class. - Creates NgspiceWindow and runs the process @@ -17,35 +17,33 @@ class NgspiceWidget(QtWidgets.QWidget): :param netlist: The file .cir.out file that contains the instructions. :type netlist: str - :param startSimulation: A function that disables - the toolbar buttons of connects the finishSimulation - function to finished.connect - :type startSimulation: function + :param simEndSignal: A signal that will be emitted to Application class + for enabling simulation interaction and plotting data if the + simulation is successful + :type simEndSignal: PyQt Signal """ QtWidgets.QWidget.__init__(self) self.obj_appconfig = Appconfig() - self.process = QtCore.QProcess(self) self.projDir = self.obj_appconfig.current_project["ProjectName"] - self.args = ['-b', '-r', netlist.replace(".cir.out", ".raw"), - netlist] + self.args = ['-b', '-r', netlist.replace(".cir.out", ".raw"), netlist] + print("Argument to ngspice: ", self.args) + + self.process = QtCore.QProcess(self) self.terminalUi = TerminalUi.TerminalUi(self.process, self.args) self.layout = QtWidgets.QVBoxLayout(self) self.layout.addWidget(self.terminalUi) -# print("Argument to ngspice command : ", netlist) - -# This variable makes sure that finished.connect is called exactly once - self.process.isFinishConnected = False - - self.process.\ - started.\ - connect(lambda: - startSimulation(process=self.process, - function=self.finishSimulation)) self.process.setWorkingDirectory(self.projDir) + self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels) + self.process.readyRead.connect(self.readyReadAll) + self.process.finished.connect( + lambda exitCode, exitStatus: + self.finishSimulation(exitCode, exitStatus, simEndSignal) + ) + self.process.errorOccurred.connect( + lambda: self.finishSimulation(None, None, simEndSignal)) self.process.start('ngspice', self.args) - self.process.readyReadStandardOutput.connect( - lambda: self.readyReadAll()) + self.obj_appconfig.process_obj.append(self.process) print(self.obj_appconfig.proc_dict) ( @@ -54,79 +52,101 @@ class NgspiceWidget(QtWidgets.QWidget): self.process.pid()) ) - if os.name != "nt": + if os.name != "nt": # Linux OS self.gawProcess = QtCore.QProcess(self) self.gawCommand = "gaw " + netlist.replace(".cir.out", ".raw") self.gawProcess.start('sh', ['-c', self.gawCommand]) print(self.gawCommand) - def finishSimulation(self, exitCode, - exitStatus, checkNgspiceProcessFinished): - """This function is intended to run when the ngspice + @QtCore.pyqtSlot() + def readyReadAll(self): + """Outputs the ngspice process standard output and standard error + to :class:`TerminalUi.TerminalUi` console + """ + self.terminalUi.simulationConsole.insertPlainText( + str(self.process.readAllStandardOutput().data(), encoding='utf-8') + ) + + stderror = str(self.process.readAllStandardError().data(), + encoding='utf-8') + + # Suppressing the Ngspice PrinterOnly error that batch mode throws + stderror = '\n'.join([errLine for errLine in stderror.split('\n') + if ('PrinterOnly' not in errLine and + 'viewport for graphics' not in errLine)]) + + self.terminalUi.simulationConsole.insertPlainText(stderror) + + def finishSimulation(self, exitCode, exitStatus, simEndSignal): + """This function is intended to run when the Ngspice simulation finishes. It singals to the function that generates the plots and also writes in the appropriate status of the simulation (Whether it was a success or not). - :param exitCode: The exit code signal of the qprocess + :param exitCode: The exit code signal of the QProcess that runs ngspice :type exitCode: int :param exitStatus: The exit status signal of the qprocess that runs ngspice :type exitStatus: class:`QtCore.QProcess.ExitStatus` - :param checkNgspiceProcessFinished: Takes the plotting function - as input and uses it to generate the plots. The reason - why this is passed in such a way is to minimize the no. - of functions passed through a chain of objects. - :type checkNgspiceProcessFinished: function + :param simEndSignal: A signal passed from constructor + for enabling simulation interaction and plotting data if the + simulation is successful + :type simEndSignal: PyQt Signal """ - -# To stop progressbar from running after simulation is completed + # Stop progressbar from running after simulation is completed self.terminalUi.progressBar.setMaximum(100) self.terminalUi.progressBar.setProperty("value", 100) + self.terminalUi.cancelSimulationButton.setEnabled(False) + self.terminalUi.redoSimulationButton.setEnabled(True) + + if exitCode is None: + exitCode = self.process.exitCode() - if exitStatus == QtCore.QProcess.NormalExit: - checkNgspiceProcessFinished(exitCode) + errorType = self.process.error() + if errorType < 3: # 0, 1, 2 ==> failed to start, crashed, timedout + exitStatus = QtCore.QProcess.CrashExit + elif exitStatus is None: + exitStatus = self.process.exitStatus() + # Redo-simulation does not set correct exit status and code. + # So, need to check the error type: + # => UnknownError along with NormalExit seems successful simulation + if exitStatus == QtCore.QProcess.NormalExit and exitCode == 0 \ + and errorType == QtCore.QProcess.UnknownError: + successFormat = '<span style="color:#00ff00; font-size:26px;">\ + {} \ + </span>' + self.terminalUi.simulationConsole.append( + successFormat.format("Simulation Completed Successfully!")) + + else: failedFormat = '<span style="color:#ff3333; font-size:26px;"> \ - {} \ - </span>' - successFormat = '<span style="color:#00ff00; font-size:26px;"> \ - {} \ - </span>' - if exitCode == 0: - self.terminalUi.simulationConsole.append( - successFormat.format("Simulation Completed Successfully!")) + {} \ + </span>' + self.terminalUi.simulationConsole.append( + failedFormat.format("Simulation Failed!")) + + errMsg = 'Simulation ' + if errorType == QtCore.QProcess.FailedToStart: + errMsg += 'failed to start. ' + \ + 'Ensure that eSim is installed correctly.' + elif errorType == QtCore.QProcess.Crashed: + errMsg += 'crashed. Try again later.' + elif errorType == QtCore.QProcess.Timedout: + errMsg += ' has timed out. Try to reduce the ' + \ + ' simulation time or the simulation step interval.' else: - self.terminalUi.simulationConsole.append( - failedFormat.format("Simulation Failed!")) + errMsg += ' could not complete. Try again later.' - self.terminalUi.simulationConsole.verticalScrollBar().setValue( - self.terminalUi.simulationConsole.verticalScrollBar().maximum() - ) - else: self.msg = QtWidgets.QErrorMessage() self.msg.setModal(True) self.msg.setWindowTitle("Error Message") - self.msg.showMessage( - 'Ngspice simulation did not complete successfully.' - ) + self.msg.showMessage(errMsg) self.msg.exec_() - @QtCore.pyqtSlot() - def readyReadAll(self): - """Outputs the ngspice process standard output and standard error - to :class:`TerminalUi.TerminalUi` console - """ - self.terminalUi.simulationConsole.insertPlainText( - str(self.process.readAllStandardOutput().data(), encoding='utf-8') + self.terminalUi.simulationConsole.verticalScrollBar().setValue( + self.terminalUi.simulationConsole.verticalScrollBar().maximum() ) - stderror = str(self.process.readAllStandardError().data(), - encoding='utf-8') -# For suppressing the PrinterOnly error that batch mode throws - stderror = '\n'.join([line for line in stderror.split('\n') - if ('PrinterOnly' not in line and - 'viewport for graphics' not in line)]) - self.terminalUi.simulationConsole.insertPlainText( - stderror - ) + simEndSignal.emit(exitStatus, exitCode) |