diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/frontEnd/Application.py | 143 | ||||
-rwxr-xr-x | src/frontEnd/DockArea.py | 188 | ||||
-rw-r--r-- | src/frontEnd/TerminalUi.py | 143 | ||||
-rw-r--r-- | src/frontEnd/TerminalUi.ui | 163 | ||||
-rwxr-xr-x | src/kicadtoNgspice/Convert.py | 154 | ||||
-rwxr-xr-x | src/kicadtoNgspice/DeviceModel.py | 136 | ||||
-rw-r--r-- | src/kicadtoNgspice/KicadtoNgspice.py | 138 | ||||
-rw-r--r-- | src/kicadtoNgspice/Microcontroller.py | 283 | ||||
-rw-r--r-- | src/kicadtoNgspice/Model.py | 224 | ||||
-rw-r--r-- | src/kicadtoNgspice/Processing.py | 2 | ||||
-rw-r--r-- | src/kicadtoNgspice/TrackWidget.py | 2 | ||||
-rw-r--r-- | src/ngspiceSimulation/NgspiceWidget.py | 199 |
12 files changed, 1357 insertions, 418 deletions
diff --git a/src/frontEnd/Application.py b/src/frontEnd/Application.py index 795b5bf9..96be7ca4 100644 --- a/src/frontEnd/Application.py +++ b/src/frontEnd/Application.py @@ -12,12 +12,15 @@ # AUTHOR: Fahim Khan, fahim.elex@gmail.com # 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: Tuesday 13 September 2022 +# REVISION: Wednesday 07 June 2023 # ========================================================================= import os +import sys +import shutil import traceback if os.name == 'nt': @@ -28,20 +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 time -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() @@ -551,109 +554,59 @@ 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(): + @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 exitStatus == QtCore.QProcess.NormalExit and exitCode == 0: 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 + self.obj_Mainview.obj_dockarea.plottingEditor() + except Exception as e: + self.msg = QtWidgets.QErrorMessage() + self.msg.setModal(True) + self.msg.setWindowTitle("Error Message") + self.msg.showMessage( + '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 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) 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 - currTime = time.time() - count = 0 - while True: - try: - # if os.name == 'nt': - # proc = 'mintty' - # else: - # proc = 'xterm' - - # Edited by Sumanto Kar 25/08/2021 - if os.name != 'nt' and \ - self.checkIfProcessRunning('xterm') is False: - self.msg = QtWidgets.QErrorMessage() - self.msg.setModal(True) - self.msg.setWindowTitle("Warning Message") - self.msg.showMessage( - 'Simulation was interrupted/failed. ' - 'Please close all the Ngspice windows ' - 'and then rerun the simulation.' - ) - self.msg.exec_() - return + self.obj_Mainview.obj_dockarea.ngspiceEditor( + projName, ngspiceNetlist, self.simulationEndSignal) - st = os.stat(os.path.join(self.projDir, "plot_data_i.txt")) - if st.st_mtime >= currTime: - break - except Exception: - pass - time.sleep(1) - - # Fail Safe ===> - count += 1 - if count >= 10: - print( - "Ngspice taking too long for simulation. " - "Check netlist file (*.cir.out) " - "to change simulation parameters." - ) - - self.msg = QtWidgets.QErrorMessage() - self.msg.setModal(True) - self.msg.setWindowTitle("Warning Message") - self.msg.showMessage( - 'Ngspice taking too long for simulation. ' - 'Check netlist file (*.cir.out) ' - 'to change simulation parameters.' - ) - self.msg.exec_() - - return - - # Calling Python Plotting - try: - self.obj_Mainview.obj_dockarea.plottingEditor() - except Exception as e: - self.msg = QtWidgets.QErrorMessage() - 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.' - ) - self.msg.exec_() - print("Exception Message:", str(e), traceback.format_exc()) - self.obj_appconfig.print_error('Exception Message : ' + str(e)) + self.ngspice.setEnabled(False) + self.conversion.setEnabled(False) + self.closeproj.setEnabled(False) + self.wrkspce.setEnabled(False) else: self.msg = QtWidgets.QErrorMessage() @@ -756,7 +709,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 461240b9..7037dcfd 100755 --- a/src/frontEnd/DockArea.py +++ b/src/frontEnd/DockArea.py @@ -89,6 +89,7 @@ class DockArea(QtWidgets.QMainWindow): """This function create widget for interactive PythonPlotting.""" self.projDir = self.obj_appconfig.current_project["ProjectName"] self.projName = os.path.basename(self.projDir) + dockName = f'Plotting-{self.projName}-' # self.project = os.path.join(self.projDir, self.projName) global count @@ -99,66 +100,65 @@ class DockArea(QtWidgets.QMainWindow): # Adding to main Layout self.plottingWidget.setLayout(self.plottingLayout) - dock['Plotting-' + str(count) - ] = QtWidgets.QDockWidget('Plotting-' + str(count)) - dock['Plotting-' + str(count)].setWidget(self.plottingWidget) + dock[dockName + str(count) + ] = QtWidgets.QDockWidget(dockName + + str(count)) + dock[dockName + str(count)] \ + .setWidget(self.plottingWidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['Plotting-' + str(count)]) - self.tabifyDockWidget(dock['Welcome'], dock['Plotting-' + str(count)]) + dock[dockName + str(count)]) + self.tabifyDockWidget(dock['Welcome'], + dock[dockName + str(count)]) - dock['Plotting-' + str(count)].setVisible(True) - dock['Plotting-' + str(count)].setFocus() - dock['Plotting-' + str(count)].raise_() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() temp = self.obj_appconfig.current_project['ProjectName'] if temp: self.obj_appconfig.dock_dict[temp].append( - dock['Plotting-' + str(count)] + dock[dockName + str(count)] ) count = count + 1 - def ngspiceEditor(self, projDir): + def ngspiceEditor(self, projName, netlist, simEndSignal): """ This function creates widget for Ngspice window.""" - self.projDir = projDir - self.projName = os.path.basename(self.projDir) - 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, self.projDir) + NgspiceWidget(netlist, simEndSignal) ) # Adding to main Layout self.ngspiceWidget.setLayout(self.ngspiceLayout) - dock['NgSpice-' + str(count) - ] = QtWidgets.QDockWidget('NgSpice-' + str(count)) - dock['NgSpice-' + str(count)].setWidget(self.ngspiceWidget) + dockName = f'Simulation-{projName}-' + dock[dockName + str(count) + ] = QtWidgets.QDockWidget(dockName + + str(count)) + dock[dockName + str(count)] \ + .setWidget(self.ngspiceWidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['NgSpice-' + str(count)]) - self.tabifyDockWidget(dock['Welcome'], dock['NgSpice-' + str(count)]) + dock[dockName + str(count)]) + self.tabifyDockWidget(dock['Welcome'], + dock[dockName + + str(count)]) # CSS - dock['NgSpice-' + str(count)].setStyleSheet(" \ + dock[dockName + str(count)].setStyleSheet(" \ .QWidget { border-radius: 15px; border: 1px solid gray; padding: 0px;\ width: 200px; height: 150px; } \ ") - dock['NgSpice-' + str(count)].setVisible(True) - dock['NgSpice-' + str(count)].setFocus() - dock['NgSpice-' + str(count)].raise_() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() temp = self.obj_appconfig.current_project['ProjectName'] if temp: self.obj_appconfig.dock_dict[temp].append( - dock['NgSpice-' + str(count)] + dock[dockName + str(count)] ) count = count + 1 @@ -166,6 +166,11 @@ class DockArea(QtWidgets.QMainWindow): """This function defines UI for model editor.""" print("in model editor") global count + + projDir = self.obj_appconfig.current_project["ProjectName"] + projName = os.path.basename(projDir) + dockName = f'Model Editor-{projName}-' + self.modelwidget = QtWidgets.QWidget() self.modellayout = QtWidgets.QVBoxLayout() @@ -174,23 +179,25 @@ class DockArea(QtWidgets.QMainWindow): # Adding to main Layout self.modelwidget.setLayout(self.modellayout) - dock['Model Editor-' + - str(count)] = QtWidgets.QDockWidget('Model Editor-' + str(count)) - dock['Model Editor-' + str(count)].setWidget(self.modelwidget) + dock[dockName + + str(count)] = QtWidgets.QDockWidget(dockName + + str(count)) + dock[dockName + str(count)] \ + .setWidget(self.modelwidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['Model Editor-' + str(count)]) + dock[dockName + str(count)]) self.tabifyDockWidget(dock['Welcome'], - dock['Model Editor-' + str(count)]) + dock[dockName + str(count)]) # CSS - dock['Model Editor-' + str(count)].setStyleSheet(" \ + dock[dockName + str(count)].setStyleSheet(" \ .QWidget { border-radius: 15px; border: 1px solid gray; \ padding: 5px; width: 200px; height: 150px; } \ ") - dock['Model Editor-' + str(count)].setVisible(True) - dock['Model Editor-' + str(count)].setFocus() - dock['Model Editor-' + str(count)].raise_() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() count = count + 1 @@ -199,91 +206,109 @@ class DockArea(QtWidgets.QMainWindow): This function is creating Editor UI for Kicad to Ngspice conversion. """ global count + + projDir = self.obj_appconfig.current_project["ProjectName"] + projName = os.path.basename(projDir) + dockName = f'Netlist-{projName}-' + self.kicadToNgspiceWidget = QtWidgets.QWidget() self.kicadToNgspiceLayout = QtWidgets.QVBoxLayout() self.kicadToNgspiceLayout.addWidget(MainWindow(clarg1, clarg2)) self.kicadToNgspiceWidget.setLayout(self.kicadToNgspiceLayout) - dock['kicadToNgspice-' + str(count)] = \ - QtWidgets.QDockWidget('kicadToNgspice-' + str(count)) - dock['kicadToNgspice-' + + dock[dockName + str(count)] = \ + QtWidgets.QDockWidget(dockName + str(count)) + dock[dockName + str(count)].setWidget(self.kicadToNgspiceWidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['kicadToNgspice-' + str(count)]) + dock[dockName + str(count)]) self.tabifyDockWidget(dock['Welcome'], - dock['kicadToNgspice-' + str(count)]) + dock[dockName + str(count)]) # CSS - dock['kicadToNgspice-' + str(count)].setStyleSheet(" \ + dock[dockName + str(count)].setStyleSheet(" \ .QWidget { border-radius: 15px; border: 1px solid gray;\ padding: 5px; width: 200px; height: 150px; } \ ") - dock['kicadToNgspice-' + str(count)].setVisible(True) - dock['kicadToNgspice-' + str(count)].setFocus() - dock['kicadToNgspice-' + str(count)].raise_() - dock['kicadToNgspice-' + str(count)].activateWindow() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() + dock[dockName + str(count)].activateWindow() temp = self.obj_appconfig.current_project['ProjectName'] if temp: self.obj_appconfig.dock_dict[temp].append( - dock['kicadToNgspice-' + str(count)] + dock[dockName + str(count)] ) count = count + 1 def subcircuiteditor(self): """This function creates a widget for different subcircuit options.""" global count + + projDir = self.obj_appconfig.current_project["ProjectName"] + projName = os.path.basename(projDir) + dockName = f'Subcircuit-{projName}-' + self.subcktWidget = QtWidgets.QWidget() self.subcktLayout = QtWidgets.QVBoxLayout() self.subcktLayout.addWidget(Subcircuit(self)) self.subcktWidget.setLayout(self.subcktLayout) - dock['Subcircuit-' + - str(count)] = QtWidgets.QDockWidget('Subcircuit-' + str(count)) - dock['Subcircuit-' + str(count)].setWidget(self.subcktWidget) + dock[dockName + + str(count)] = QtWidgets.QDockWidget(dockName + + str(count)) + dock[dockName + str(count)] \ + .setWidget(self.subcktWidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['Subcircuit-' + str(count)]) + dock[dockName + str(count)]) self.tabifyDockWidget(dock['Welcome'], - dock['Subcircuit-' + str(count)]) + dock[dockName + str(count)]) # CSS - dock['Subcircuit-' + str(count)].setStyleSheet(" \ + dock[dockName + str(count)].setStyleSheet(" \ .QWidget { border-radius: 15px; border: 1px solid gray;\ padding: 5px; width: 200px; height: 150px; } \ ") - dock['Subcircuit-' + str(count)].setVisible(True) - dock['Subcircuit-' + str(count)].setFocus() - dock['Subcircuit-' + str(count)].raise_() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() count = count + 1 def makerchip(self): """This function creates a widget for different subcircuit options.""" global count + + projDir = self.obj_appconfig.current_project["ProjectName"] + projName = os.path.basename(projDir) + dockName = f'Makerchip-{projName}-' + self.makerWidget = QtWidgets.QWidget() self.makerLayout = QtWidgets.QVBoxLayout() self.makerLayout.addWidget(makerchip(self)) self.makerWidget.setLayout(self.makerLayout) - dock['Makerchip-' + - str(count)] = QtWidgets.QDockWidget('Makerchip-' + str(count)) - dock['Makerchip-' + str(count)].setWidget(self.makerWidget) + dock[dockName + + str(count)] = QtWidgets.QDockWidget(dockName + + str(count)) + dock[dockName + str(count)].setWidget(self.makerWidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['Makerchip-' + str(count)]) + dock[dockName + str(count)]) self.tabifyDockWidget(dock['Welcome'], - dock['Makerchip-' + str(count)]) + dock[dockName + str(count)]) # CSS - dock['Makerchip-' + str(count)].setStyleSheet(" \ + dock[dockName + str(count)].setStyleSheet(" \ .QWidget { border-radius: 15px; border: 1px solid gray;\ padding: 5px; width: 200px; height: 150px; } \ ") - dock['Makerchip-' + str(count)].setVisible(True) - dock['Makerchip-' + str(count)].setFocus() - dock['Makerchip-' + str(count)].raise_() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() count = count + 1 @@ -318,31 +343,38 @@ class DockArea(QtWidgets.QMainWindow): def modelicaEditor(self, projDir): """This function sets up the UI for ngspice to modelica conversion.""" global count + + projName = os.path.basename(projDir) + dockName = f'Modelica-{projName}-' + self.modelicaWidget = QtWidgets.QWidget() self.modelicaLayout = QtWidgets.QVBoxLayout() self.modelicaLayout.addWidget(OpenModelicaEditor(projDir)) self.modelicaWidget.setLayout(self.modelicaLayout) - dock['Modelica-' + str(count) - ] = QtWidgets.QDockWidget('Modelica-' + str(count)) - dock['Modelica-' + str(count)].setWidget(self.modelicaWidget) + dock[dockName + str(count) + ] = QtWidgets.QDockWidget(dockName + str(count)) + dock[dockName + str(count)] \ + .setWidget(self.modelicaWidget) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, - dock['Modelica-' + str(count)]) - self.tabifyDockWidget(dock['Welcome'], dock['Modelica-' + str(count)]) + dock[dockName + + str(count)]) + self.tabifyDockWidget(dock['Welcome'], dock[dockName + + str(count)]) - dock['Modelica-' + str(count)].setVisible(True) - dock['Modelica-' + str(count)].setFocus() - dock['Modelica-' + str(count)].raise_() + dock[dockName + str(count)].setVisible(True) + dock[dockName + str(count)].setFocus() + dock[dockName + str(count)].raise_() # CSS - dock['Modelica-' + str(count)].setStyleSheet(" \ + dock[dockName + str(count)].setStyleSheet(" \ .QWidget { border-radius: 15px; border: 1px solid gray;\ padding: 5px; width: 200px; height: 150px; } \ ") temp = self.obj_appconfig.current_project['ProjectName'] if temp: self.obj_appconfig.dock_dict[temp].append( - dock['Modelica-' + str(count)] + dock[dockName + str(count)] ) count = count + 1 diff --git a/src/frontEnd/TerminalUi.py b/src/frontEnd/TerminalUi.py new file mode 100644 index 00000000..4c53548f --- /dev/null +++ b/src/frontEnd/TerminalUi.py @@ -0,0 +1,143 @@ +from PyQt5 import QtCore, QtGui, QtWidgets, uic +import os + + +class TerminalUi(QtWidgets.QMainWindow): + """This is a class that represents the GUI required to provide + details regarding the ngspice simulation. This GUI consists of + a progress bar, a console window which displays the log of the + simulation and button required for re-simulation and cancellation + of the simulation""" + def __init__(self, qProcess, args): + """The constructor of the TerminalUi class + param: qProcess: a PyQt QProcess that runs ngspice + type: qProcess: :class:`QtCore.QProcess` + param: args: arguments to be passed on to the ngspice call + type: args: list + """ + super(TerminalUi, self).__init__() + + # Other variables + self.darkColor = True + self.qProcess = qProcess + self.args = args + self.iconDir = "../../images" + + # Load the ui file + uic.loadUi("TerminalUi.ui", self) + + # Define Our Widgets + self.progressBar = self.findChild( + QtWidgets.QProgressBar, + "progressBar" + ) + self.simulationConsole = self.findChild( + QtWidgets.QTextEdit, + "simulationConsole" + ) + + self.lightDarkModeButton = self.findChild( + QtWidgets.QPushButton, + "lightDarkModeButton" + ) + self.cancelSimulationButton = self.findChild( + QtWidgets.QPushButton, + "cancelSimulationButton" + ) + self.cancelSimulationButton.setEnabled(True) + + self.redoSimulationButton = self.findChild( + QtWidgets.QPushButton, + "redoSimulationButton" + ) + self.redoSimulationButton.setEnabled(False) + + # Add functionalities to Widgets + self.lightDarkModeButton.setIcon( + QtGui.QIcon( + os.path.join( + self.iconDir, + 'light_mode.png' + ) + ) + ) + self.lightDarkModeButton.clicked.connect(self.changeColor) + self.cancelSimulationButton.clicked.connect(self.cancelSimulation) + self.redoSimulationButton.clicked.connect(self.redoSimulation) + + self.simulationCancelled = False + self.show() + + def cancelSimulation(self): + """This function cancels the ongoing ngspice simulation. + """ + self.cancelSimulationButton.setEnabled(False) + self.redoSimulationButton.setEnabled(True) + + if (self.qProcess.state() == QtCore.QProcess.NotRunning): + return + + self.simulationCancelled = True + self.qProcess.kill() + + # To show progressBar completed + self.progressBar.setMaximum(100) + self.progressBar.setProperty("value", 100) + + cancelFormat = '<span style="color:#FF8624; font-size:26px;">{}</span>' + self.simulationConsole.append( + cancelFormat.format("Simulation Cancelled!")) + self.simulationConsole.verticalScrollBar().setValue( + self.simulationConsole.verticalScrollBar().maximum() + ) + + def redoSimulation(self): + """This function reruns the ngspice simulation + """ + self.cancelSimulationButton.setEnabled(True) + self.redoSimulationButton.setEnabled(False) + + if (self.qProcess.state() != QtCore.QProcess.NotRunning): + return + + # To make the progressbar running + self.progressBar.setMaximum(0) + self.progressBar.setProperty("value", -1) + + self.simulationConsole.setText("") + self.simulationCancelled = False + + self.qProcess.start('ngspice', self.args) + + def changeColor(self): + """Toggles the :class:`Ui_Form` console between dark mode + and light mode + """ + if self.darkColor is True: + self.simulationConsole.setStyleSheet("QTextEdit {\n \ + background-color: white;\n \ + color: black;\n \ + }") + self.lightDarkModeButton.setIcon( + QtGui.QIcon( + os.path.join( + self.iconDir, + "dark_mode.png" + ) + ) + ) + self.darkColor = False + else: + self.simulationConsole.setStyleSheet("QTextEdit {\n \ + background-color: rgb(36, 31, 49);\n \ + color: white;\n \ + }") + self.lightDarkModeButton.setIcon( + QtGui.QIcon( + os.path.join( + self.iconDir, + "light_mode.png" + ) + ) + ) + self.darkColor = True diff --git a/src/frontEnd/TerminalUi.ui b/src/frontEnd/TerminalUi.ui new file mode 100644 index 00000000..9039984d --- /dev/null +++ b/src/frontEnd/TerminalUi.ui @@ -0,0 +1,163 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TerminalUi</class> + <widget class="QWidget" name="TerminalUi"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1244</width> + <height>644</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <widget class="QWidget" name="verticalLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>1131</width> + <height>471</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>15</number> + </property> + <property name="topMargin"> + <number>15</number> + </property> + <property name="rightMargin"> + <number>15</number> + </property> + <property name="bottomMargin"> + <number>15</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>35</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">QProgressBar::chunk { + background-color: rgb(54,158,225); +}</string> + </property> + <property name="maximum"> + <number>0</number> + </property> + <property name="value"> + <number>-1</number> + </property> + <property name="format"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="redoSimulationButton"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>35</height> + </size> + </property> + <property name="text"> + <string>Resimulate</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelSimulationButton"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>35</height> + </size> + </property> + <property name="text"> + <string>Cancel Simulation</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="lightDarkModeButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>35</width> + <height>35</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTextEdit" name="simulationConsole"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>400</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">QTextEdit { + background-color: rgb(36, 31, 49); + color: white; +}</string> + </property> + <property name="html"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/kicadtoNgspice/Convert.py b/src/kicadtoNgspice/Convert.py index 566182e0..66f8a7c0 100755 --- a/src/kicadtoNgspice/Convert.py +++ b/src/kicadtoNgspice/Convert.py @@ -1,9 +1,11 @@ -from PyQt5 import QtWidgets import os import shutil -from . import TrackWidget from xml.etree import ElementTree as ET +from PyQt5 import QtWidgets + +from . import TrackWidget + class Convert: """ @@ -67,9 +69,8 @@ class Convert: theta_val = str(self.entry_var[self.end].text()) if len( str(self.entry_var[self.end].text())) > 0 else '0' self.addline = self.addline.partition( - '(')[0] + "(" + vo_val + " " + va_val + " " +\ - freq_val + " " + td_val + " " +\ - theta_val + ")" + '(')[0] + "(" + vo_val + " " + va_val + " " + \ + freq_val + " " + td_val + " " + theta_val + ")" self.sourcelistvalue.append([self.index, self.addline]) except BaseException: print( @@ -102,8 +103,8 @@ class Convert: str(self.entry_var[self.end].text())) > 0 else '0' self.addline = self.addline.partition( - '(')[0] + "(" + v1_val + " " + v2_val + " " +\ - td_val + " " + tr_val + " " + tf_val + " " +\ + '(')[0] + "(" + v1_val + " " + v2_val + " " + \ + td_val + " " + tr_val + " " + tf_val + " " + \ pw_val + " " + tp_val + ")" self.sourcelistvalue.append([self.index, self.addline]) except BaseException: @@ -183,8 +184,8 @@ class Convert: str(self.entry_var[self.end].text())) > 0 else '0' self.addline = self.addline.partition( - '(')[0] + "(" + v1_val + " " + v2_val + " " +\ - td1_val + " " + tau1_val + " " + td2_val +\ + '(')[0] + "(" + v1_val + " " + v2_val + " " + \ + td1_val + " " + tau1_val + " " + td2_val + \ " " + tau2_val + ")" self.sourcelistvalue.append([self.index, self.addline]) except BaseException: @@ -403,18 +404,21 @@ class Convert: if num_turns2 == "": num_turns2 = "620" addmodelLine = ".model " + \ - line[3] + \ - "_primary lcouple (num_turns= " + num_turns + ")" + line[3] + \ + "_primary lcouple (num_turns= " + \ + num_turns + ")" modelParamValue.append( [line[0], addmodelLine, "*primary lcouple"]) addmodelLine = ".model " + \ - line[3] + "_iron_core core (" + bh_array + \ - " area = " + area + " length =" + length + ")" + line[3] + "_iron_core core (" + bh_array + \ + " area = " + area + " length =" + length + \ + ")" modelParamValue.append( [line[0], addmodelLine, "*iron core"]) addmodelLine = ".model " + \ - line[3] + \ - "_secondary lcouple (num_turns =" + num_turns2 + ")" + line[3] + \ + "_secondary lcouple (num_turns =" + \ + num_turns2 + ")" modelParamValue.append( [line[0], addmodelLine, "*secondary lcouple"]) except Exception as e: @@ -456,8 +460,8 @@ class Convert: default = 0 # Checking if value is iterable.its for vector if ( - not isinstance(value, str) and - hasattr(value, '__iter__') + not isinstance(value, str) and + hasattr(value, '__iter__') ): addmodelLine += param + "=[" for lineVar in value: @@ -500,6 +504,122 @@ class Convert: return schematicInfo + def addMicrocontrollerParameter(self, schematicInfo): + """ + This function adds the Microcontroller Model details to schematicInfo + """ + + # Create object of TrackWidget + self.obj_track = TrackWidget.TrackWidget() + + # List to store model line + addmodelLine = [] + modelParamValue = [] + + for line in self.obj_track.microcontrollerTrack: + # print "Model Track :",line + try: + start = line[7] + # end = line[8] + addmodelLine = ".model " + line[3] + " " + line[2] + "(" + z = 0 + for key, value in line[9].items(): + # Checking for default value and accordingly assign + # param and default. + if ':' in key: + key = key.split(':') + param = key[0] + default = key[1] + else: + param = key + default = 0 + # Checking if value is iterable.its for vector + if ( + not isinstance(value, str) and + hasattr(value, '__iter__') + ): + addmodelLine += param + "=[" + for lineVar in value: + if str( + self.obj_track.microcontroller_var + [lineVar].text()) == "": + paramVal = default + else: + paramVal = str( + self.obj_track.microcontroller_var + [lineVar].text()) + # Checks For 5th Parameter(Hex File Path) + if z == 4: + chosen_file_path = paramVal + star_file_path = chosen_file_path + star_count = 0 + for c in chosen_file_path: + # If character is uppercase + if c.isupper(): + c_in = chosen_file_path.index(c) + c_in += star_count + # Adding asterisks(*) to the path + # around the character + star_file_path = \ + star_file_path[ + :c_in] + "*" + star_file_path[ + c_in] + "**" + star_file_path[ + c_in + 1:] + star_count += 3 + + paramVal = "\"" + star_file_path + "\"" + + addmodelLine += paramVal + " " + z = z + 1 + addmodelLine += "] " + else: + if str( + self.obj_track.microcontroller_var + [value].text()) == "": + paramVal = default + else: + paramVal = str( + self.obj_track.microcontroller_var + [value].text()) + # Checks For 5th Parameter(Hex File Path) + if z == 4: + chosen_file_path = paramVal + star_file_path = chosen_file_path + star_count = 0 + for c in chosen_file_path: + # If character is uppercase + if c.isupper(): + c_in = chosen_file_path.index(c) + c_in += star_count + # Adding asterisks(*) to the path around + # the character + star_file_path = \ + star_file_path[:c_in] + "*" + \ + star_file_path[c_in] + "**" + \ + star_file_path[c_in + 1:] + star_count += 3 + + paramVal = "\"" + star_file_path + "\"" + z = z + 1 + addmodelLine += param + "=" + paramVal + " " + + addmodelLine += ") " + modelParamValue.append([line[0], addmodelLine, line[4]]) + except Exception as e: + print("Caught an exception in microcontroller ", line[1]) + print("Exception Message : ", str(e)) + + # Adding it to schematic + for item in modelParamValue: + if ".ic" in item[1]: + schematicInfo.insert(0, item[1]) + schematicInfo.insert(0, item[2]) + else: + schematicInfo.append(item[2]) # Adding Comment + schematicInfo.append(item[1]) # Adding model line + + return schematicInfo + def addDeviceLibrary(self, schematicInfo, kicadFile): """ This function add the library details to schematicInfo diff --git a/src/kicadtoNgspice/DeviceModel.py b/src/kicadtoNgspice/DeviceModel.py index 7fb9776e..ccbe8986 100755 --- a/src/kicadtoNgspice/DeviceModel.py +++ b/src/kicadtoNgspice/DeviceModel.py @@ -15,6 +15,8 @@ class DeviceModel(QtWidgets.QWidget): - d DIODE - j JFET - m MOSFET + - s SWITCH + - tx single lossy transmission line - Other 2 functions same as the ones in subCircuit - trackLibrary - trackLibraryWithoutButton @@ -436,6 +438,131 @@ class DeviceModel(QtWidgets.QWidget): self.grid.addWidget(jfetbox) # Adding Device Details # + # Increment row and widget count + self.row = self.row + 1 + self.devicemodel_dict_end[words[0]] = self.count + self.count = self.count + 1 + + elif eachline[0] == 's': + # print("Device Model Switch:", words[0]) + self.devicemodel_dict_beg[words[0]] = self.count + switchbox = QtWidgets.QGroupBox() + switchgrid = QtWidgets.QGridLayout() + switchbox.setTitle( + "Add library for Switch " + + words[0] + + " : " + + words[5]) + self.entry_var[self.count] = QtWidgets.QLineEdit() + self.entry_var[self.count].setText("") + # global path_name + try: + for child in root: + if child.tag == words[0]: + # print("DEVICE MODEL MATCHING---", \ + # child.tag, words[0]) + try: + if child[0].text \ + and os.path.exists(child[0].text): + path_name = child[0].text + self.entry_var[self.count] \ + .setText(child[0].text) + else: + self.entry_var[self.count].setText("") + except BaseException as e: + print("Error when set text of device " + + "model switch :", str(e)) + except BaseException: + pass + + switchgrid.addWidget(self.entry_var[self.count], self.row, 1) + self.addbtn = QtWidgets.QPushButton("Add") + self.addbtn.setObjectName("%d" % self.count) + self.addbtn.clicked.connect(self.trackLibrary) + self.deviceDetail[self.count] = words[0] + + if self.entry_var[self.count].text() == "": + pass + else: + self.trackLibraryWithoutButton(self.count, path_name) + + switchgrid.addWidget(self.addbtn, self.row, 2) + switchbox.setLayout(switchgrid) + + # CSS + switchbox.setStyleSheet(" \ + QGroupBox { border: 1px solid gray; border-radius: \ + 9px; margin-top: 0.5em; } \ + QGroupBox::title { subcontrol-origin: margin; left:\ + 10px; padding: 0 3px 0 3px; } \ + ") + + self.grid.addWidget(switchbox) + + # Adding Device Details # + + # Increment row and widget count + self.row = self.row + 1 + self.devicemodel_dict_end[words[0]] = self.count + self.count = self.count + 1 + + elif eachline[0] == 'ytxl': + # print("Device Model ymod:", words[0]) + self.devicemodel_dict_beg[words[0]] = self.count + ymodbox = QtWidgets.QGroupBox() + ymodgrid = QtWidgets.QGridLayout() + ymodbox.setTitle( + "Add library for ymod " + + words[0] + + " : " + + words[4]) + self.entry_var[self.count] = QtWidgets.QLineEdit() + self.entry_var[self.count].setText("") + # global path_name + try: + for child in root: + if child.tag == words[0]: + # print("DEVICE MODEL MATCHING---", \ + # child.tag, words[0]) + try: + if child[0].text \ + and os.path.exists(child[0].text): + path_name = child[0].text + self.entry_var[self.count] \ + .setText(child[0].text) + else: + self.entry_var[self.count].setText("") + except BaseException as e: + print("Error when set text of device " + + "model ymod :", str(e)) + except BaseException: + pass + + ymodgrid.addWidget(self.entry_var[self.count], self.row, 1) + self.addbtn = QtWidgets.QPushButton("Add") + self.addbtn.setObjectName("%d" % self.count) + self.addbtn.clicked.connect(self.trackLibrary) + self.deviceDetail[self.count] = words[0] + + if self.entry_var[self.count].text() == "": + pass + else: + self.trackLibraryWithoutButton(self.count, path_name) + + ymodgrid.addWidget(self.addbtn, self.row, 2) + ymodbox.setLayout(ymodgrid) + + # CSS + ymodbox.setStyleSheet(" \ + QGroupBox { border: 1px solid gray; border-radius: \ + 9px; margin-top: 0.5em; } \ + QGroupBox::title { subcontrol-origin: margin; left:\ + 10px; padding: 0 3px 0 3px; } \ + ") + + self.grid.addWidget(ymodbox) + + # Adding Device Details # # Increment row and widget count self.row = self.row + 1 @@ -443,6 +570,7 @@ class DeviceModel(QtWidgets.QWidget): self.count = self.count + 1 elif eachline[0] == 'm': + self.devicemodel_dict_beg[words[0]] = self.count mosfetbox = QtWidgets.QGroupBox() mosfetgrid = QtWidgets.QGridLayout() @@ -452,7 +580,7 @@ class DeviceModel(QtWidgets.QWidget): "Add library for MOSFET " + words[0] + " : " + - words[5]) + words[4]) self.entry_var[self.count] = QtWidgets.QLineEdit() self.entry_var[self.count].setText("") self.entry_var[self.count].setReadOnly(True) @@ -512,12 +640,12 @@ class DeviceModel(QtWidgets.QWidget): # print("DEVICE MODEL MATCHING---", \ # child.tag, words[0]) while i <= end: - self.entry_var[i].setText(child[i-beg].text) + self.entry_var[i].setText(child[i - beg].text) if (i - beg) == 0: if os.path.exists(child[0].text): self.entry_var[i] \ - .setText(child[i-beg].text) - path_name = child[i-beg].text + .setText(child[i - beg].text) + path_name = child[i - beg].text else: self.entry_var[i].setText("") i = i + 1 diff --git a/src/kicadtoNgspice/KicadtoNgspice.py b/src/kicadtoNgspice/KicadtoNgspice.py index 231efd52..e018143f 100644 --- a/src/kicadtoNgspice/KicadtoNgspice.py +++ b/src/kicadtoNgspice/KicadtoNgspice.py @@ -13,21 +13,24 @@ # MODIFIED: Rahul Paknikar, rahulp@iitb.ac.in # ORGANIZATION: eSim Team at FOSSEE, IIT Bombay # CREATED: Wednesday 04 March 2015 -# REVISION: Sunday 18 September 2022 +# REVISION: Tuesday 25 April 2023 # ========================================================================= -import sys import os +import sys +from xml.etree import ElementTree as ET + from PyQt5 import QtWidgets -from .Processing import PrcocessNetlist + from . import Analysis -from . import Source -from . import Model +from . import Convert from . import DeviceModel +from . import Model +from . import Microcontroller +from . import Source from . import SubcircuitTab -from . import Convert from . import TrackWidget -from xml.etree import ElementTree as ET +from .Processing import PrcocessNetlist class MainWindow(QtWidgets.QWidget): @@ -93,10 +96,11 @@ class MainWindow(QtWidgets.QWidget): schematicInfo, sourcelist) # List storing model detail - global modelList, outputOption,\ - unknownModelList, multipleModelList, plotText + global modelList, outputOption, unknownModelList, multipleModelList, \ + plotText, microcontrollerList modelList = [] + microcontrollerList = [] outputOption = [] plotText = [] ( @@ -109,8 +113,10 @@ class MainWindow(QtWidgets.QWidget): ) = obj_proc.convertICintoBasicBlocks( schematicInfo, outputOption, modelList, plotText ) - # print("=======================================") - # print("Model available in the Schematic :", modelList) + for line in modelList: + if line[6] == "Nghdl": + microcontrollerList.append(line) + modelList.remove(line) """ - Checking if any unknown model is used in schematic which is not @@ -124,7 +130,7 @@ class MainWindow(QtWidgets.QWidget): self.msg.setModal(True) self.msg.setWindowTitle("Unknown Models") self.content = "Your schematic contain unknown model " + \ - ', '.join(unknownModelList) + ', '.join(unknownModelList) self.msg.showMessage(self.content) self.msg.exec_() @@ -134,7 +140,7 @@ class MainWindow(QtWidgets.QWidget): self.msg.setWindowTitle("Multiple Models") self.mcontent = "Look like you have duplicate model in \ modelParamXML directory " + \ - ', '.join(multipleModelList[0]) + ', '.join(multipleModelList[0]) self.msg.showMessage(self.mcontent) self.msg.exec_() @@ -179,6 +185,9 @@ class MainWindow(QtWidgets.QWidget): - Subcircuits => obj_subcircuitTab => SubcircuitTab.SubcircuitTab(`schematicInfo`,`path_to_projFile`) + - Microcontrollers => obj_microcontroller + => Model.Model(schematicInfo, microcontrollerList, self.clarg1) + - Finally pass each of these objects, to widgets - convertWindow > mainLayout > tabWidgets > AnalysisTab, SourceTab ... """ @@ -213,6 +222,12 @@ class MainWindow(QtWidgets.QWidget): schematicInfo, self.clarg1) self.subcircuitTab.setWidget(obj_subcircuitTab) self.subcircuitTab.setWidgetResizable(True) + global obj_microcontroller + self.microcontrollerTab = QtWidgets.QScrollArea() + obj_microcontroller = Microcontroller.\ + Microcontroller(schematicInfo, microcontrollerList, self.clarg1) + self.microcontrollerTab.setWidget(obj_microcontroller) + self.microcontrollerTab.setWidgetResizable(True) self.tabWidget = QtWidgets.QTabWidget() # self.tabWidget.TabShape(QtWidgets.QTabWidget.Rounded) @@ -221,6 +236,7 @@ class MainWindow(QtWidgets.QWidget): self.tabWidget.addTab(self.modelTab, "Ngspice Model") self.tabWidget.addTab(self.deviceModelTab, "Device Modeling") self.tabWidget.addTab(self.subcircuitTab, "Subcircuits") + self.tabWidget.addTab(self.microcontrollerTab, "Microcontroller") self.mainLayout = QtWidgets.QVBoxLayout() self.mainLayout.addWidget(self.tabWidget) # self.mainLayout.addStretch(1) @@ -247,9 +263,9 @@ class MainWindow(QtWidgets.QWidget): try: fr = open( - os.path.join( - projpath, project_name + "_Previous_Values.xml"), 'r' - ) + os.path.join( + projpath, project_name + "_Previous_Values.xml"), 'r' + ) temp_tree = ET.parse(fr) temp_root = temp_tree.getroot() except BaseException: @@ -552,7 +568,8 @@ class MainWindow(QtWidgets.QWidget): for grand_child in child: if i <= end: grand_child.text = \ - str(obj_model.obj_trac.model_entry_var[i].text()) + str(obj_model.obj_trac.model_entry_var[ + i].text()) i = i + 1 tmp_check = 1 @@ -568,16 +585,16 @@ class MainWindow(QtWidgets.QWidget): ET.SubElement( attr_ui, "field" + str(i + 1), name=item ).text = str( - obj_model.obj_trac.model_entry_var[i].text() - ) + obj_model.obj_trac.model_entry_var[i].text() + ) i = i + 1 else: ET.SubElement( attr_ui, "field" + str(i + 1), name=value ).text = str( - obj_model.obj_trac.model_entry_var[i].text() - ) + obj_model.obj_trac.model_entry_var[i].text() + ) i = i + 1 # Writing Device Model values @@ -596,7 +613,7 @@ class MainWindow(QtWidgets.QWidget): while it <= end: ET.SubElement(attr_var, "field").text = \ - str(obj_devicemodel.entry_var[it].text()) + str(obj_devicemodel.entry_var[it].text()) it = it + 1 # Writing Subcircuit values @@ -615,9 +632,70 @@ class MainWindow(QtWidgets.QWidget): while it <= end: ET.SubElement(attr_var, "field").text = \ - str(obj_subcircuitTab.entry_var[it].text()) + str(obj_subcircuitTab.entry_var[it].text()) it = it + 1 + # Writing for Microcontroller + if check == 0: + attr_microcontroller = ET.SubElement(attr_parent, + "microcontroller") + if check == 1: + for child in attr_parent: + if child.tag == "microcontroller": + attr_microcontroller = child + i = 0 + + # tmp_check is a variable to check for duplicates in the xml file + tmp_check = 0 + # tmp_i is the iterator in case duplicates are there; + # then in that case we need to replace only the child node and + # not create a new parent node + + for line in microcontrollerList: + tmp_check = 0 + for rand_itr in obj_microcontroller.obj_trac.microcontrollerTrack: + if rand_itr[2] == line[2] and rand_itr[3] == line[3]: + start = rand_itr[7] + end = rand_itr[8] + + i = start + for child in attr_microcontroller: + if child.text == line[2] and child.tag == line[3]: + for grand_child in child: + if i <= end: + grand_child.text = \ + str( + obj_microcontroller. + obj_trac.microcontroller_var[i].text()) + i = i + 1 + tmp_check = 1 + + if tmp_check == 0: + attr_ui = ET.SubElement(attr_microcontroller, line[3], + name="type") + attr_ui.text = line[2] + for key, value in line[7].items(): + if ( + hasattr(value, '__iter__') and + i <= end and not isinstance(value, str) + ): + for item in value: + ET.SubElement( + attr_ui, "field" + str(i + 1), name=item + ).text = str( + obj_microcontroller. + obj_trac.microcontroller_var[i].text() + ) + i = i + 1 + else: + ET.SubElement( + attr_ui, "field" + str(i + 1), name=value + ).text = str( + obj_microcontroller.obj_trac.microcontroller_var[ + i].text() + ) + i = i + 1 + # xml written to previous value file for the project tree = ET.ElementTree(attr_parent) tree.write(fw) @@ -650,6 +728,12 @@ class MainWindow(QtWidgets.QWidget): print("=========================================================") print("Netlist After Adding Ngspice Model :", store_schematicInfo) + store_schematicInfo = self.obj_convert.addMicrocontrollerParameter( + store_schematicInfo) + print("=========================================================") + print("Netlist After Adding Microcontroller Model :", + store_schematicInfo) + # Adding Device Library to SchematicInfo store_schematicInfo = self.obj_convert.addDeviceLibrary( store_schematicInfo, self.kicadFile) @@ -761,14 +845,14 @@ class MainWindow(QtWidgets.QWidget): words = eachline.split() option = words[0] if (option == '.ac' or option == '.dc' or option == - '.disto' or option == '.noise' or - option == '.op' or option == '.pz' or option == - '.sens' or option == '.tf' or + '.disto' or option == '.noise' or + option == '.op' or option == '.pz' or option == + '.sens' or option == '.tf' or option == '.tran'): analysisOption.append(eachline + '\n') elif (option == '.save' or option == '.print' or option == - '.plot' or option == '.four'): + '.plot' or option == '.four'): eachline = eachline.strip('.') outputOption.append(eachline + '\n') elif (option == '.nodeset' or option == '.ic'): diff --git a/src/kicadtoNgspice/Microcontroller.py b/src/kicadtoNgspice/Microcontroller.py new file mode 100644 index 00000000..a9360147 --- /dev/null +++ b/src/kicadtoNgspice/Microcontroller.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import os +import random +from configparser import ConfigParser +from xml.etree import ElementTree as ET + +from PyQt5 import QtWidgets, QtCore + +from . import TrackWidget + + +# Created By Vatsal Patel on 01/07/2022 + +class Microcontroller(QtWidgets.QWidget): + """ + - This class creates Model Tab of KicadtoNgspice window. + The widgets are created dynamically in the Model Tab. + """ + + def addHex(self): + """ + This function is use to keep track of all Device Model widget + """ + if os.name == 'nt': + self.home = os.path.join('library', 'config') + else: + self.home = os.path.expanduser('~') + + self.parser = ConfigParser() + self.parser.read(os.path.join( + self.home, os.path.join('.nghdl', 'config.ini'))) + self.nghdl_home = self.parser.get('NGHDL', 'NGHDL_HOME') + + self.hexfile = QtCore.QDir.toNativeSeparators( + QtWidgets.QFileDialog.getOpenFileName( + self, "Open Hex Directory", os.path.expanduser('~'), + "HEX files (*.hex);;Text files (*.txt)" + )[0] + ) + + if not self.hexfile: + """If no path is selected by user function returns""" + return + + chosen_file_path = os.path.abspath(self.hexfile) + btn = self.sender() + + # If path is selected the clicked button is stored in btn variable and + # checked from list of buttons to add the file path to correct + # QLineEdit + + if btn in self.hex_btns: + if "Add Hex File" in self.sender().text(): + self.obj_trac.microcontroller_var[ + 4 + (5 * self.hex_btns.index(btn))].setText( + chosen_file_path) + + def __init__( + self, + schematicInfo, + modelList, + clarg1, + ): + + QtWidgets.QWidget.__init__(self) + + # Processing for getting previous values + + kicadFile = clarg1 + (projpath, filename) = os.path.split(kicadFile) + project_name = os.path.basename(projpath) + check = 1 + try: + f = open( + os.path.join(projpath, project_name + "_Previous_Values.xml"), + "r", + ) + tree = ET.parse(f) + parent_root = tree.getroot() + for parent in parent_root: + if parent.tag == "microcontroller": + self.root = parent + except BaseException: + + check = 0 + print("Microcontroller Previous Values XML is Empty") + + # Creating track widget object + + self.obj_trac = TrackWidget.TrackWidget() + + # for increasing row and counting/tracking line edit widget + + self.nextrow = 0 + self.nextcount = 0 + + # for storing line edit details position details + + self.start = 0 + self.end = 0 + self.entry_var = [] + self.hex_btns = [] + self.text = "" + + # Creating GUI dynamically for Model tab + + self.grid = QtWidgets.QGridLayout() + self.setLayout(self.grid) + + for line in modelList: + # print "ModelList Item:",line + # Adding title label for model + # Key: Tag name,Value:Entry widget number + + tag_dict = {} + modelbox = QtWidgets.QGroupBox() + modelgrid = QtWidgets.QGridLayout() + modelbox.setTitle(line[5]) + self.start = self.nextcount + + # line[7] is parameter dictionary holding parameter tags. + + i = 0 + for (key, value) in line[7].items(): + # Check if value is iterable + + if not isinstance(value, str) and hasattr(value, "__iter__"): + + # For tag having vector value + + temp_tag = [] + for item in value: + + paramLabel = QtWidgets.QLabel(item) + modelgrid.addWidget(paramLabel, self.nextrow, 0) + self.obj_trac.microcontroller_var[ + self.nextcount + ] = QtWidgets.QLineEdit() + self.obj_trac.microcontroller_var[ + self.nextcount] = QtWidgets.QLineEdit() + self.obj_trac.microcontroller_var[ + self.nextcount].setText("") + + if "Enter Instance ID (Between 0-99)" in value: + self.obj_trac.microcontroller_var[ + self.nextcount].hide() + self.obj_trac.microcontroller_var[ + self.nextcount].setText( + str(random.randint(0, 99))) + else: + modelgrid.addWidget(paramLabel, self.nextrow, 0) + + if "Path of your .hex file" in value: + self.obj_trac.microcontroller_var[ + self.nextcount].setReadOnly(True) + addbtn = QtWidgets.QPushButton("Add Hex File") + addbtn.setObjectName("%d" % self.nextcount) + addbtn.clicked.connect(self.addHex) + modelgrid.addWidget(addbtn, self.nextrow, 2) + modelbox.setLayout(modelgrid) + self.hex_btns.append(addbtn) + try: + for child in root: + if ( + child.text == line[2] + and child.tag == line[3] + ): + self.obj_trac.microcontroller_var[ + self.nextcount].setText(child[i].text) + i = i + 1 + except BaseException: + print("Passes previous values") + + modelgrid.addWidget( + self.obj_trac.microcontroller_var[self.nextcount], + self.nextrow, + 1, ) + + temp_tag.append(self.nextcount) + self.nextcount = self.nextcount + 1 + self.nextrow = self.nextrow + 1 + + tag_dict[key] = temp_tag + + else: + + paramLabel = QtWidgets.QLabel(value) + self.obj_trac.microcontroller_var[ + self.nextcount + ] = QtWidgets.QLineEdit() + self.obj_trac.microcontroller_var[ + self.nextcount] = QtWidgets.QLineEdit() + self.obj_trac.microcontroller_var[self.nextcount].setText( + "") + + if "Enter Instance ID (Between 0-99)" in value: + self.obj_trac.microcontroller_var[ + self.nextcount].hide() + self.obj_trac.microcontroller_var[ + self.nextcount].setText(str(random.randint(0, 99))) + else: + modelgrid.addWidget(paramLabel, self.nextrow, 0) + + if "Path of your .hex file" in value: + self.obj_trac.microcontroller_var[ + self.nextcount].setReadOnly(True) + addbtn = QtWidgets.QPushButton("Add Hex File") + addbtn.setObjectName("%d" % self.nextcount) + addbtn.clicked.connect(self.addHex) + modelgrid.addWidget(addbtn, self.nextrow, 2) + modelbox.setLayout(modelgrid) + self.hex_btns.append(addbtn) + + # CSS + + modelbox.setStyleSheet( + " \ + QGroupBox { border: 1px solid gray; border-radius:\ + 9px; margin-top: 0.5em; } \ + QGroupBox::title { subcontrol-origin: margin; left:\ + 10px; padding: 0 3px 0 3px; } \ + " + ) + self.grid.addWidget(modelbox) + + try: + for child in root: + if child.text == line[2] and child.tag == line[3]: + self.obj_trac.microcontroller_var[ + self.nextcount].setText(child[i].text) + i = i + 1 + + except BaseException: + print("Passes previous values") + + modelgrid.addWidget( + self.obj_trac.microcontroller_var[self.nextcount], + self.nextrow, + 1, ) + + tag_dict[key] = self.nextcount + self.nextcount = self.nextcount + 1 + self.nextrow = self.nextrow + 1 + + self.end = self.nextcount - 1 + modelbox.setLayout(modelgrid) + + # CSS + + modelbox.setStyleSheet( + " \ + QGroupBox { border: 1px solid gray; border-radius: \ + 9px; margin-top: 0.5em; } \ + QGroupBox::title { subcontrol-origin: margin; left:\ + 10px; padding: 0 3px 0 3px; } \ + " + ) + + self.grid.addWidget(modelbox) + + # This keeps the track of Microcontroller Tab Widget + + lst = [ + line[0], + line[1], + line[2], + line[3], + line[4], + line[5], + line[6], + self.start, + self.end, + tag_dict, + ] + check = 0 + for itr in self.obj_trac.microcontrollerTrack: + if itr == lst: + check = 1 + if check == 0: + self.obj_trac.microcontrollerTrack.append(lst) + + self.show() diff --git a/src/kicadtoNgspice/Model.py b/src/kicadtoNgspice/Model.py index 75c0eaf5..55a988c0 100644 --- a/src/kicadtoNgspice/Model.py +++ b/src/kicadtoNgspice/Model.py @@ -1,69 +1,27 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from PyQt5 import QtWidgets, QtCore -from . import TrackWidget -from xml.etree import ElementTree as ET import os +from xml.etree import ElementTree as ET +from PyQt5 import QtWidgets +from . import TrackWidget -class Model(QtWidgets.QWidget): +class Model(QtWidgets.QWidget): """ - This class creates Model Tab of KicadtoNgspice window. The widgets are created dynamically in the Model Tab. """ - - # by Sumanto and Jay - def addHex(self): - """ - This function is use to keep track of all Device Model widget - """ - - # print("Calling Track Device Model Library funtion") - - init_path = "../../../" - if os.name == "nt": - init_path = "" - - self.hexfile = QtCore.QDir.toNativeSeparators( - QtWidgets.QFileDialog.getOpenFileName( - self, "Open Hex Directory", init_path + "home", "*.hex" - )[0] - ) - self.text = open(self.hexfile).read() - - # By Sumanto and Jay - def uploadHex(self): - """ - This function is use to keep track of all Device Model widget - """ - - # print("Calling Track Device Model Library funtion") - - path1 = os.path.expanduser("~") - path2 = "/ngspice-nghdl/src/xspice/icm/ghdl" - init_path = path1 + path2 - if os.name == "nt": - init_path = "" - - self.hexloc = QtWidgets.QFileDialog.getExistingDirectory( - self, "Open Hex Directory", init_path - ) - self.file = open(self.hexloc + "/hex.txt", "w") - self.file.write(self.text) - self.file.close() - def __init__( - self, - schematicInfo, - modelList, - clarg1, + self, + schematicInfo, + modelList, + clarg1, ): QtWidgets.QWidget.__init__(self) # Processing for getting previous values - kicadFile = clarg1 (projpath, filename) = os.path.split(kicadFile) project_name = os.path.basename(projpath) @@ -79,33 +37,27 @@ class Model(QtWidgets.QWidget): if child.tag == "model": root = child except BaseException: - check = 0 - print("Model Previous Values XML is Empty") # Creating track widget object - self.obj_trac = TrackWidget.TrackWidget() # for increasing row and counting/tracking line edit widget - self.nextrow = 0 self.nextcount = 0 # for storing line edit details position details - self.start = 0 self.end = 0 - self.entry_var = {} + self.entry_var = [] + self.hex_btns = [] self.text = "" # Creating GUI dynamically for Model tab - self.grid = QtWidgets.QGridLayout() self.setLayout(self.grid) for line in modelList: - # print "ModelList Item:",line # Adding title label for model # Key: Tag name,Value:Entry widget number @@ -115,99 +67,79 @@ class Model(QtWidgets.QWidget): modelgrid = QtWidgets.QGridLayout() modelbox.setTitle(line[5]) self.start = self.nextcount + self.model_name = line[2] # line[7] is parameter dictionary holding parameter tags. - i = 0 for (key, value) in line[7].items(): + print(value) + print(key) # Check if value is iterable - if not isinstance(value, str) and hasattr(value, "__iter__"): - # For tag having vector value - temp_tag = [] for item in value: + paramLabel = QtWidgets.QLabel(item) modelgrid.addWidget(paramLabel, self.nextrow, 0) self.obj_trac.model_entry_var[ self.nextcount ] = QtWidgets.QLineEdit() - modelgrid.addWidget( - self.obj_trac.model_entry_var[self.nextcount], - self.nextrow, - 1, - ) + + self.obj_trac.model_entry_var[ + self.nextcount] = QtWidgets.QLineEdit() + self.obj_trac.model_entry_var[self.nextcount].setText( + "") try: for child in root: if ( - child.text == line[2] - and child.tag == line[3] + child.text == line[2] + and child.tag == line[3] ): self.obj_trac.model_entry_var [self.nextcount].setText(child[i].text) + self.entry_var[self.count].setText( + child[0].text) i = i + 1 except BaseException: pass + modelgrid.addWidget(self.entry_var[self.nextcount], + self.nextrow, 1) + + modelgrid.addWidget( + self.obj_trac.model_entry_var[self.nextcount], + self.nextrow, + 1, ) temp_tag.append(self.nextcount) self.nextcount = self.nextcount + 1 self.nextrow = self.nextrow + 1 - if "upload_hex_file:1" in tag_dict: - self.addbtn = QtWidgets.QPushButton("Add Hex File") - self.addbtn.setObjectName("%d" % self.nextcount) - self.addbtn.clicked.connect(self.addHex) - modelgrid.addWidget(self.addbtn, self.nextrow, 2) - modelbox.setLayout(modelgrid) - - # CSS - - modelbox.setStyleSheet( - " \ - QGroupBox { border: 1px solid gray; border-radius:\ - 9px; margin-top: 0.5em; } \ - QGroupBox::title {subcontrol-origin: margin; left:\ - 10px; padding: 0 3px 0 3px; } \ - " - ) - - self.grid.addWidget(modelbox) - self.addbtn = QtWidgets.QPushButton( - "Upload Hex File" - ) - self.addbtn.setObjectName("%d" % self.nextcount) - self.addbtn.clicked.connect(self.uploadHex) - modelgrid.addWidget(self.addbtn, self.nextrow, 3) - modelbox.setLayout(modelgrid) - - # CSS - - modelbox.setStyleSheet( - " \ - QGroupBox { border: 1px solid gray; border-radius:\ - 9px; margin-top: 0.5em; } \ - QGroupBox::title {subcontrol-origin: margin; left:\ - 10px; padding: 0 3px 0 3px; } \ - " - ) - - self.grid.addWidget(modelbox) tag_dict[key] = temp_tag - else: + else: paramLabel = QtWidgets.QLabel(value) modelgrid.addWidget(paramLabel, self.nextrow, 0) self.obj_trac.model_entry_var[ self.nextcount ] = QtWidgets.QLineEdit() - modelgrid.addWidget( - self.obj_trac.model_entry_var[self.nextcount], - self.nextrow, - 1, + + self.obj_trac.model_entry_var[ + self.nextcount] = QtWidgets.QLineEdit() + self.obj_trac.model_entry_var[self.nextcount].setText("") + + # CSS + modelbox.setStyleSheet( + " \ + QGroupBox { border: 1px solid gray; border-radius:\ + 9px; margin-top: 0.5em; } \ + QGroupBox::title { subcontrol-origin: margin; left:\ + 10px; padding: 0 3px 0 3px; } \ + " ) + self.grid.addWidget(modelbox) try: for child in root: @@ -215,56 +147,27 @@ class Model(QtWidgets.QWidget): self.obj_trac.model_entry_var[ self.nextcount ].setText(child[i].text) + self.entry_var[self.count].setText( + child[0].text) i = i + 1 except BaseException: pass + modelgrid.addWidget(self.entry_var[self.nextcount], + self.nextrow, 1) + modelgrid.addWidget( + self.obj_trac.model_entry_var[self.nextcount], + self.nextrow, + 1, ) + tag_dict[key] = self.nextcount self.nextcount = self.nextcount + 1 self.nextrow = self.nextrow + 1 - if "upload_hex_file:1" in tag_dict: - self.addbtn = QtWidgets.QPushButton("Add Hex File") - self.addbtn.setObjectName("%d" % self.nextcount) - self.addbtn.clicked.connect(self.addHex) - modelgrid.addWidget(self.addbtn, self.nextrow, 2) - modelbox.setLayout(modelgrid) - - # CSS - - modelbox.setStyleSheet( - " \ - QGroupBox { border: 1px solid gray; border-radius:\ - 9px; margin-top: 0.5em; } \ - QGroupBox::title { subcontrol-origin: margin; left:\ - 10px; padding: 0 3px 0 3px; } \ - " - ) - - self.grid.addWidget(modelbox) - self.addbtn = QtWidgets.QPushButton("Upload Hex File") - self.addbtn.setObjectName("%d" % self.nextcount) - self.addbtn.clicked.connect(self.uploadHex) - modelgrid.addWidget(self.addbtn, self.nextrow, 3) - modelbox.setLayout(modelgrid) - - # CSS - - modelbox.setStyleSheet( - " \ - QGroupBox { border: 1px solid gray; border-radius:\ - 9px; margin-top: 0.5em; } \ - QGroupBox::title { subcontrol-origin: margin; left:\ - 10px; padding: 0 3px 0 3px; } \ - " - ) - - self.grid.addWidget(modelbox) self.end = self.nextcount - 1 modelbox.setLayout(modelgrid) # CSS - modelbox.setStyleSheet( " \ QGroupBox { border: 1px solid gray; border-radius: \ @@ -277,7 +180,6 @@ class Model(QtWidgets.QWidget): self.grid.addWidget(modelbox) # This keeps the track of Model Tab Widget - lst = [ line[0], line[1], @@ -298,3 +200,21 @@ class Model(QtWidgets.QWidget): self.obj_trac.modelTrack.append(lst) self.show() + + def add_hex_btn(self, modelgrid, modelbox): + self.addbtn = QtWidgets.QPushButton("Add Hex File") + self.addbtn.setObjectName("%d" % self.nextcount) + self.addbtn.clicked.connect(self.addHex) + modelgrid.addWidget(self.addbtn, self.nextrow, 2) + modelbox.setLayout(modelgrid) + + # CSS + modelbox.setStyleSheet( + " \ + QGroupBox { border: 1px solid gray; border-radius:\ + 9px; margin-top: 0.5em; } \ + QGroupBox::title { subcontrol-origin: margin; left:\ + 10px; padding: 0 3px 0 3px; } \ + " + ) + self.grid.addWidget(modelbox) diff --git a/src/kicadtoNgspice/Processing.py b/src/kicadtoNgspice/Processing.py index a0b20ada..11c95965 100644 --- a/src/kicadtoNgspice/Processing.py +++ b/src/kicadtoNgspice/Processing.py @@ -408,7 +408,7 @@ class PrcocessNetlist: # Insert comment at remove line schematicInfo.insert(index, "* " + compline) comment = "* Schematic Name:\ - " + compType + ", NgSpice Name: " + modelname + " + compType + ", Ngspice Name: " + modelname # Here instead of adding compType(use for XML), # added modelName(Unique Model Name) modelList.append( diff --git a/src/kicadtoNgspice/TrackWidget.py b/src/kicadtoNgspice/TrackWidget.py index 3a8b0dac..9f1d07c2 100644 --- a/src/kicadtoNgspice/TrackWidget.py +++ b/src/kicadtoNgspice/TrackWidget.py @@ -24,7 +24,9 @@ class TrackWidget: op_check = [] # Track widget for Model detail modelTrack = [] + microcontrollerTrack = [] model_entry_var = {} + microcontroller_var = {} # Track Widget for Device Model detail deviceModelTrack = {} diff --git a/src/ngspiceSimulation/NgspiceWidget.py b/src/ngspiceSimulation/NgspiceWidget.py index 8c63a22a..94368cdd 100644 --- a/src/ngspiceSimulation/NgspiceWidget.py +++ b/src/ngspiceSimulation/NgspiceWidget.py @@ -1,58 +1,169 @@ +import os from PyQt5 import QtWidgets, QtCore from configuration.Appconfig import Appconfig -from configparser import ConfigParser -import os +from frontEnd import TerminalUi # This Class creates NgSpice Window class NgspiceWidget(QtWidgets.QWidget): - def __init__(self, command, projPath): + def __init__(self, netlist, simEndSignal): """ - Creates constructor for NgspiceWidget class. - - Checks whether OS is Linux or Windows and - creates Ngspice window accordingly. + - Creates NgspiceWindow and runs the process + - Calls the logs the ngspice process, returns + it's simulation status and calls the plotter + - Checks whether it is Linux and runs gaw + :param netlist: The file .cir.out file that + contains the instructions. + :type netlist: str + :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.projDir = self.obj_appconfig.current_project["ProjectName"] + self.args = ['-b', '-r', netlist.replace(".cir.out", ".raw"), netlist] + print("Argument to ngspice: ", self.args) + self.process = QtCore.QProcess(self) - self.terminal = QtWidgets.QWidget(self) + self.terminalUi = TerminalUi.TerminalUi(self.process, self.args) self.layout = QtWidgets.QVBoxLayout(self) - self.layout.addWidget(self.terminal) - - print("Argument to ngspice command : ", command) - - if os.name == 'nt': # For Windows OS - parser_nghdl = ConfigParser() - parser_nghdl.read( - os.path.join('library', 'config', '.nghdl', 'config.ini') - ) - - msys_home = parser_nghdl.get('COMPILER', 'MSYS_HOME') - - tempdir = os.getcwd() - projPath = self.obj_appconfig.current_project["ProjectName"] - os.chdir(projPath) - self.command = 'cmd /c '+'"start /min ' + \ - msys_home + "/usr/bin/mintty.exe ngspice -p " + command + '"' - self.process.start(self.command) - os.chdir(tempdir) - - else: # For Linux OS - self.command = "cd " + projPath + \ - ";ngspice -r " + command.replace(".cir.out", ".raw") + \ - " " + command - # Creating argument for process - self.args = ['-hold', '-e', self.command] - self.process.start('xterm', self.args) - self.obj_appconfig.process_obj.append(self.process) - print(self.obj_appconfig.proc_dict) - ( - self.obj_appconfig.proc_dict - [self.obj_appconfig.current_project['ProjectName']].append( - self.process.pid()) - ) - self.process = QtCore.QProcess(self) - self.command = "gaw " + command.replace(".cir.out", ".raw") - self.process.start('sh', ['-c', self.command]) - print(self.command) + self.layout.addWidget(self.terminalUi) + + 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, False) + ) + self.process.errorOccurred.connect( + lambda: self.finishSimulation(None, None, simEndSignal, True)) + self.process.start('ngspice', self.args) + + self.obj_appconfig.process_obj.append(self.process) + print(self.obj_appconfig.proc_dict) + ( + self.obj_appconfig.proc_dict + [self.obj_appconfig.current_project['ProjectName']].append( + self.process.pid()) + ) + + 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) + + @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, hasErrorOccurred): + """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 + 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 simEndSignal: A signal passed from constructor + for enabling simulation interaction and plotting data if the + simulation is successful + :type simEndSignal: PyQt Signal + """ + + # Canceling simulation triggers both finished and + # errorOccurred signals...need to skip finished signal in this case. + if not hasErrorOccurred and self.terminalUi.simulationCancelled: + return + + # 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() + + 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() + + if self.terminalUi.simulationCancelled: + msg = QtWidgets.QMessageBox() + msg.setModal(True) + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setWindowTitle("Warning Message") + msg.setText("Simulation was cancelled.") + msg.setStandardButtons(QtWidgets.QMessageBox.Ok) + msg.exec() + + elif exitStatus == QtCore.QProcess.NormalExit and exitCode == 0 \ + and errorType == QtCore.QProcess.UnknownError: + # Redo-simulation does not set correct exit status and code. + # So, need to check the error type ==> + # UnknownError along with NormalExit seems successful simulation + + 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>' + 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: + errMsg += ' could not complete. Try again later.' + + msg = QtWidgets.QErrorMessage() + msg.setModal(True) + msg.setWindowTitle("Error Message") + msg.showMessage(errMsg) + msg.exec() + + self.terminalUi.simulationConsole.verticalScrollBar().setValue( + self.terminalUi.simulationConsole.verticalScrollBar().maximum() + ) + + simEndSignal.emit(exitStatus, exitCode) |