summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRahul P2023-06-11 16:46:32 +0530
committerGitHub2023-06-11 16:46:32 +0530
commit9a5f3dabc357277b384c51ccf047f5580772f454 (patch)
treeafc4cf7c8d461576f03393445f3741eecfa650ee
parentb145afdb869564df4131f0b0b472116ca744ef65 (diff)
parent4eea06f6fcd654c7f0919f395ff42fabbafa0171 (diff)
downloadeSim-9a5f3dabc357277b384c51ccf047f5580772f454.tar.gz
eSim-9a5f3dabc357277b384c51ccf047f5580772f454.tar.bz2
eSim-9a5f3dabc357277b384c51ccf047f5580772f454.zip
Merge pull request #243 from pranavkaruvally/master
Send Ngspice simulation to background
-rw-r--r--images/dark_mode.pngbin0 -> 951 bytes
-rw-r--r--images/light_mode.pngbin0 -> 989 bytes
-rw-r--r--src/frontEnd/Application.py145
-rwxr-xr-xsrc/frontEnd/DockArea.py188
-rw-r--r--src/frontEnd/TerminalUi.py143
-rw-r--r--src/frontEnd/TerminalUi.ui163
-rw-r--r--src/ngspiceSimulation/NgspiceWidget.py199
7 files changed, 620 insertions, 218 deletions
diff --git a/images/dark_mode.png b/images/dark_mode.png
new file mode 100644
index 00000000..a29b53f6
--- /dev/null
+++ b/images/dark_mode.png
Binary files differ
diff --git a/images/light_mode.png b/images/light_mode.png
new file mode 100644
index 00000000..b40872c4
--- /dev/null
+++ b/images/light_mode.png
Binary files differ
diff --git a/src/frontEnd/Application.py b/src/frontEnd/Application.py
index 7588b1a1..790bf779 100644
--- a/src/frontEnd/Application.py
+++ b/src/frontEnd/Application.py
@@ -10,14 +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: 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()
@@ -534,109 +537,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()
@@ -739,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 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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::NoTextInteraction</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
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)