diff --git a/.github/workflows/unit-test-ubuntu.yml b/.github/workflows/unit-test-ubuntu.yml index 7c9c0a3..9430d8c 100644 --- a/.github/workflows/unit-test-ubuntu.yml +++ b/.github/workflows/unit-test-ubuntu.yml @@ -44,7 +44,7 @@ jobs: - name: Install python packages run: pip install -r requirements.txt - name: Run unit test - run: ./hpr-sim.py -i input/unit_test.yml output/ && ls -l output/*/* + run: ./hpr-sim.py -i input/unit_test.yml output/ --headless && ls -l output/*/* pyinstaller-ubuntu: runs-on: ubuntu-22.04 needs: build-ubuntu @@ -65,4 +65,4 @@ jobs: - name: PyInstaller build run: python tools/pyinstaller_build.py - name: Run unit test - run: cd build/pyinstaller/dist/hpr-sim && ./hpr-sim -i input/unit_test.yml output/ && ls -l output/*/* + run: cd build/pyinstaller/dist/hpr-sim && ./hpr-sim -i input/unit_test.yml output/ --headless && ls -l output/*/* diff --git a/.github/workflows/unit-test-windows.yml b/.github/workflows/unit-test-windows.yml index c1b9435..4174927 100644 --- a/.github/workflows/unit-test-windows.yml +++ b/.github/workflows/unit-test-windows.yml @@ -50,7 +50,7 @@ jobs: - name: Install python packages run: pip install -r requirements.txt - name: Run unit test - run: ./hpr-sim.py -i input/unit_test.yml output/ && ls -l output/*/* + run: ./hpr-sim.py -i input/unit_test.yml output/ --headless && ls -l output/*/* pyinstaller-windows: runs-on: windows-2022 defaults: @@ -74,4 +74,4 @@ jobs: - name: PyInstaller build run: python tools/pyinstaller_build.py - name: Run unit test - run: cd build/pyinstaller/dist/hpr-sim && ./hpr-sim -i input/unit_test.yml output/ && ls -l output/*/* + run: cd build/pyinstaller/dist/hpr-sim && ./hpr-sim -i input/unit_test.yml output/ --headless && ls -l output/*/* diff --git a/hpr-sim.py b/hpr-sim.py index 292ac6f..7d1762e 100755 --- a/hpr-sim.py +++ b/hpr-sim.py @@ -7,7 +7,7 @@ import multiprocessing as mp # Path modifications -paths = ["build/src", "src/exec", "src/preproc", "src/postproc", "src/util"] +paths = ["build/src", "src/exec", "src/gui", "src/preproc", "src/postproc", "src/util"] for item in paths: addPath = pathlib.Path(__file__).parent / item @@ -15,6 +15,7 @@ # Project modules import exec +import gui_main import postproc_flight #------------------------------------------------------------------------------# @@ -26,15 +27,18 @@ # Parse CLI parser = argparse.ArgumentParser() - parser.add_argument('-i', "--input" , type=str, help="Input file path") - parser.add_argument('output', type=str, help="Output file path") + parser.add_argument("--headless", action="store_true", help="Headless mode (no GUI)") + parser.add_argument("-i", "--input", type=str, help="Input file path") + parser.add_argument("output", type=str, help="Output file path") args = parser.parse_args() inputPath = pathlib.Path(args.input) outputPath = pathlib.Path(args.output) configPath = pathlib.Path(__file__).parent / "config" - if inputPath is not None: - exec.exec(inputPath, outputPath, configPath) + if not(args.headless): + gui_main.exec() + elif inputPath is not None: + exec.run(inputPath, outputPath, configPath) else: postproc_flight.postproc(outputPath) diff --git a/requirements.txt b/requirements.txt index d63d24f..d358f85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ pyyaml tqdm colorama pyinstaller +pyqt5 +pyqtgraph diff --git a/src/exec/exec.py b/src/exec/exec.py index 87fbcab..9cfdae3 100644 --- a/src/exec/exec.py +++ b/src/exec/exec.py @@ -62,7 +62,7 @@ def cli_intro(metadata): #------------------------------------------------------------------------------# -def exec(inputPath, outputPath, configPath): +def run(inputPath, outputPath, configPath): """ Executes simluation using paramters defined in input file diff --git a/src/gui/gui_common.py b/src/gui/gui_common.py new file mode 100644 index 0000000..c4ed557 --- /dev/null +++ b/src/gui/gui_common.py @@ -0,0 +1,83 @@ +# System modules +import pathlib +import functools +from PyQt5.QtWidgets import( + QLabel, + QPushButton, + QSpinBox, + QFileDialog, + QSizePolicy +) + +from PyQt5.QtCore import Qt +import pyqtgraph as pg + +#------------------------------------------------------------------------------# + +def pyqtgraph_setup(): + + pg.setConfigOptions(background='w') + pg.setConfigOptions(foreground='k') + pg.setConfigOptions(antialias=True) + +#------------------------------------------------------------------------------# + +class Label(QLabel): + + # QLabel w/ desired default settings + + def __init__(self, text): + + super().__init__(text) + + # Center text + self.setAlignment(Qt.AlignCenter) + +class PushButton(QPushButton): + + # QPushButton w/ desired default settings + + def __init__(self, text): + + super().__init__(text) + + # Automatic rescaling in both dimensions + self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + +class SpinBox(QSpinBox): + + # QSpinBox w/ desired default settings + + def __init__(self, min, max): + + super().__init__() + + self.setMinimum(min) + self.setMaximum(max) + + spinBoxMax = 2147483647 # Enforced by QSpinBox.setMaximum + +#------------------------------------------------------------------------------# + +def action_get_file(parent, target): + return functools.partial(get_file, parent, target) + +def get_file(parent, target): + + fileName = QFileDialog.getOpenFileName(parent, "Select Input File", pathlib.Path(".").resolve().as_posix(), "Input Files (*.yml *.yaml)") + target.setText(fileName[0]) + +#------------------------------------------------------------------------------# + +def action_get_directory(parent, target): + return functools.partial(get_directory, parent, target) + +def get_directory(parent, target): + + directory = QFileDialog.getExistingDirectory(parent, "Select Output Directory", pathlib.Path(".").resolve().as_posix()) + target.setText(directory) + +#------------------------------------------------------------------------------# + +if __name__ == "__main__": + pass diff --git a/src/gui/gui_elements.py b/src/gui/gui_elements.py new file mode 100644 index 0000000..fb4d3a3 --- /dev/null +++ b/src/gui/gui_elements.py @@ -0,0 +1,408 @@ +import os +import numpy as np +import pathlib + +from PyQt5.QtWidgets import( + QLabel, + QMainWindow, + QPushButton, + QTabWidget, + QWidget, + QGridLayout, + QGroupBox, + QScrollArea, + QLineEdit, + QProgressBar, + QComboBox, + QSpinBox, + QCheckBox, + QListWidget, +) + +from PyQt5.QtGui import ( + QDoubleValidator +) + +from PyQt5.QtCore import Qt +import pyqtgraph as pg + +# Project modules +import exec +import gui_common +import postproc_flight +import util_misc + +#------------------------------------------------------------------------------# + +class MainWindow(QMainWindow): + + def __init__(self): + + super().__init__() + + self.setWindowTitle("hpr-sim") + gui_common.pyqtgraph_setup() + + # Define tab widgets + self.tabInput = TabInput() + self.tabOutput = TabOutput() + #tabRecovery = TabRecovery() + #tabPerformance = TabPerformance() + #tabOptimization = TabOptimization() + + self.tabs = QTabWidget() + self.tabs.insertTab(0, self.tabInput , "Simulation" ) + self.tabs.insertTab(1, self.tabOutput, "Data") + + self.setCentralWidget(self.tabs) + +#------------------------------------------------------------------------------# + +class TabInput(QWidget): + + def __init__(self): + + super().__init__() + + #----------------------------------------------------------------------# + + # Group: Simulation I/O + + self.groupIO = QGroupBox("Simulation I/O") + self.layoutIO = QGridLayout() + self.groupIO.setLayout(self.layoutIO) + + # Input File + self.labelInput = gui_common.Label("Input File:") + self.lineInput = QLineEdit("file/path") + self.buttonInput = gui_common.PushButton("") + self.buttonInput.clicked.connect(gui_common.action_get_file(self,self.lineInput)) + + # Output File + self.labelOutput = gui_common.Label("Output File:") + self.lineOutput = QLineEdit("file/path") + self.buttonOutput = gui_common.PushButton("") + self.buttonOutput.clicked.connect(gui_common.action_get_directory(self,self.lineOutput)) + + # Populate group layout + self.layoutIO.addWidget(self.labelInput , 0, 0) + self.layoutIO.addWidget(self.lineInput , 0, 1) + self.layoutIO.addWidget(self.buttonInput , 0, 2) + self.layoutIO.addWidget(self.labelOutput , 0, 3) + self.layoutIO.addWidget(self.lineOutput , 0, 4) + self.layoutIO.addWidget(self.buttonOutput, 0, 5) + + #----------------------------------------------------------------------# + + # Group: Simulation Parameters + + self.groupParam = QGroupBox("Simulation Parameters") + self.layoutParam = QGridLayout() + self.groupParam.setLayout(self.layoutParam) + + self.labelModel = gui_common.Label("Model:") + self.comboModel = QComboBox() + self.comboModel.insertItems(0, ["item1", "item2", "item3"]) + + self.labelParameter = gui_common.Label("Parameter:") + self.comboParameter = QComboBox() + self.comboParameter.insertItems(0, ["item1", "item2", "item3"]) + + self.labelProperties = QLabel() + self.scrollProperties = QScrollArea() + + for i in range(10): + self.labelProperties.setText(self.labelProperties.text() + f"test line {i}\n") + + self.scrollProperties.setWidget(self.labelProperties) + self.scrollProperties.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + self.scrollProperties.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.scrollProperties.setWidgetResizable(True) + + # Plot parameter distributions + self.plotDist = pg.PlotWidget() + self.plotDist.setMenuEnabled(False) + + # Populate group layout + self.layoutParam.addWidget(self.labelModel , 0, 0, 1, 1) + self.layoutParam.addWidget(self.comboModel , 0, 1, 1, 1) + self.layoutParam.addWidget(self.labelParameter , 0, 2, 1, 1) + self.layoutParam.addWidget(self.comboParameter , 0, 3, 1, 1) + self.layoutParam.addWidget(self.scrollProperties, 1, 0, 1, 4) + self.layoutParam.addWidget(self.plotDist, 0, 4, 2, 1) + + #----------------------------------------------------------------------# + + # Group: Simulation Control + + self.groupControl = QGroupBox("Simulation Control") + self.layoutControl = QGridLayout() + self.groupControl.setLayout(self.layoutControl) + + self.labelMcMode = gui_common.Label("MC Mode:") + self.comboMcMode = QComboBox() + self.comboMcMode.insertItems(0, ["nominal", "montecarlo"]) + + self.labelSeed = gui_common.Label("Seed:") + self.spinSeed = gui_common.SpinBox(0, gui_common.SpinBox.spinBoxMax) + + self.labelNumMC = gui_common.Label("Num MC:") + self.spinNumMC = gui_common.SpinBox(1, gui_common.SpinBox.spinBoxMax) + + self.labelProcMode = gui_common.Label("Proc Mode:") + self.comboProcMode = QComboBox() + self.comboProcMode.insertItems(0, ["serial", "parallel"]) + + self.labelNumProc = gui_common.Label("Num Proc:") + self.spinNumProc = gui_common.SpinBox(1, os.cpu_count()) + + self.progressBar = QProgressBar() + self.progressBar.setValue(50) + + self.labelRun = gui_common.Label("50/100") + + self.buttonRun = gui_common.PushButton("RUN") + self.buttonRun.clicked.connect(self.action_run_exec) + + # Populate group layout + self.layoutControl.addWidget(self.labelMcMode , 0, 0, 1, 1) + self.layoutControl.addWidget(self.comboMcMode , 0, 1, 1, 1) + self.layoutControl.addWidget(self.labelSeed , 0, 2, 1, 1) + self.layoutControl.addWidget(self.spinSeed , 0, 3, 1, 1) + self.layoutControl.addWidget(self.labelNumMC , 0, 4, 1, 1) + self.layoutControl.addWidget(self.spinNumMC , 0, 5, 1, 1) + self.layoutControl.addWidget(self.labelProcMode, 0, 6, 1, 1) + self.layoutControl.addWidget(self.comboProcMode, 0, 7, 1, 1) + self.layoutControl.addWidget(self.labelNumProc , 0, 8, 1, 1) + self.layoutControl.addWidget(self.spinNumProc , 0, 9, 1, 1) + self.layoutControl.addWidget(self.labelRun , 1, 10, 1, 1) + self.layoutControl.addWidget(self.progressBar , 1, 0, 1, 10) + self.layoutControl.addWidget(self.buttonRun , 0, 10, 1, 1) + + #----------------------------------------------------------------------# + + # Populate tab layout + self.layoutTab = QGridLayout() + self.layoutTab.addWidget(self.groupIO , 0, 0, 1, 1) + self.layoutTab.addWidget(self.groupParam , 1, 0, 10, 1) + self.layoutTab.addWidget(self.groupControl, 11, 0, 1, 1) + + self.setLayout(self.layoutTab) + + def action_run_exec(self): + + inputPath = pathlib.Path(self.lineInput.text()) + outputPath = pathlib.Path(self.lineOutput.text()) + subdir = "_internal" if util_misc.is_bundled() else "." + configPath = pathlib.Path(subdir) / "config" + exec.run(inputPath, outputPath, configPath) + +#------------------------------------------------------------------------------# + +class TabOutput(QWidget): + + def __init__(self): + + super().__init__() + + #----------------------------------------------------------------------# + + self.groupIO = QGroupBox("Data I/O") + self.layoutIO = QGridLayout() + self.groupIO.setLayout(self.layoutIO) + + # Output Path + self.labelOutput = gui_common.Label("Output Path:") + self.lineOutput = QLineEdit("file/path") + self.buttonOutput = gui_common.PushButton("") + self.buttonOutput.clicked.connect(gui_common.action_get_directory(self, self.lineOutput)) + + # Load data + self.buttonLoad = gui_common.PushButton("Load") + self.buttonLoad.clicked.connect(self.action_load_data) + + # Metadata + self.labelMeta = gui_common.Label("Metadata:") + self.editMeta = QLineEdit() + self.editMeta.setReadOnly(True) + + # Populate group layout + self.layoutIO.addWidget(self.labelOutput , 0, 0, 1, 1) + self.layoutIO.addWidget(self.lineOutput , 0, 1, 1, 1) + self.layoutIO.addWidget(self.buttonOutput, 0, 2, 1, 1) + self.layoutIO.addWidget(self.buttonLoad , 0, 3, 1, 1) + self.layoutIO.addWidget(self.labelMeta , 1, 0, 1 ,1) + self.layoutIO.addWidget(self.editMeta , 1, 1, 1 ,3) + + #----------------------------------------------------------------------# + + self.groupVis = QGroupBox("Data Visualization") + self.layoutVis = QGridLayout() + self.groupVis.setLayout(self.layoutVis) + + self.listFields = QListWidget() + self.listFields.currentItemChanged.connect(self.action_plot_update) + # TODO: resize to fit list contents + + self.labelUnits = gui_common.Label("Units:") + self.comboUnits = QComboBox() + self.comboUnits.currentIndexChanged.connect(self.action_plot_update_units) + + self.labelRunNum = gui_common.Label("Run Num:") + self.spinRunNum = gui_common.SpinBox(0,0) + self.spinRunNum.valueChanged.connect(self.action_plot_update) + + self.labelRunAll = gui_common.Label("Plot All:") + self.checkRunAll = QCheckBox() + self.checkRunAll.stateChanged.connect(self.action_plot_update) + + # Plot + self.plotTelem = pg.PlotWidget() + self.plotTelem.setMenuEnabled(False) + self.penTelem = pg.mkPen('b', width=1) + self.plotTelem.sigRangeChanged.connect(self.action_plot_get_range) + + # Plot axis controls + #self.buttonAxisAuto = gui_common.PushButton("Auto") + #self.buttonAxisAuto.clicked.connect(self.action_plot_auto_range) + + self.labelAxisXMin = gui_common.Label("X-Min:") + self.lineAxisXMin = QLineEdit() + self.lineAxisXMin.returnPressed.connect(self.action_plot_set_range) + self.lineAxisXMin.setValidator(QDoubleValidator()) + + self.labelAxisXMax = gui_common.Label("X-Max:") + self.lineAxisXMax = QLineEdit() + self.lineAxisXMax.returnPressed.connect(self.action_plot_set_range) + self.lineAxisXMax.setValidator(QDoubleValidator()) + + self.labelAxisYMin = gui_common.Label("Y-Min:") + self.lineAxisYMin = QLineEdit() + self.lineAxisYMin.returnPressed.connect(self.action_plot_set_range) + self.lineAxisYMin.setValidator(QDoubleValidator()) + + self.labelAxisYMax = gui_common.Label("Y-Max:") + self.lineAxisYMax = QLineEdit() + self.lineAxisYMax.returnPressed.connect(self.action_plot_set_range) + self.lineAxisYMax.setValidator(QDoubleValidator()) + + # Populate group layout + self.layoutVis.addWidget(self.listFields , 0, 0, 3, 1) + self.layoutVis.addWidget(self.labelUnits , 0, 1, 1, 1) + self.layoutVis.addWidget(self.comboUnits , 0, 2, 1, 1) + self.layoutVis.addWidget(self.labelRunNum , 0, 3, 1, 1) + self.layoutVis.addWidget(self.spinRunNum , 0, 4, 1, 1) + self.layoutVis.addWidget(self.labelRunAll , 0, 5, 1, 1) + self.layoutVis.addWidget(self.checkRunAll , 0, 6, 1, 1) + self.layoutVis.addWidget(self.plotTelem , 1, 1, 1, 8) + #self.layoutVis.addWidget(self.buttonAxisAuto, 2, 1, 1, 1) + self.layoutVis.addWidget(self.labelAxisXMin, 2, 1, 1, 1) + self.layoutVis.addWidget(self.lineAxisXMin , 2, 2, 1, 1) + self.layoutVis.addWidget(self.labelAxisXMax, 2, 3, 1, 1) + self.layoutVis.addWidget(self.lineAxisXMax , 2, 4, 1, 1) + self.layoutVis.addWidget(self.labelAxisYMin, 2, 5, 1, 1) + self.layoutVis.addWidget(self.lineAxisYMin , 2, 6, 1, 1) + self.layoutVis.addWidget(self.labelAxisYMax, 2, 7, 1, 1) + self.layoutVis.addWidget(self.lineAxisYMax , 2, 8, 1, 1) + + #----------------------------------------------------------------------# + + # Populate tab layout + layoutTab = QGridLayout() + layoutTab.addWidget(self.groupIO , 0, 0, 1, 1) + layoutTab.addWidget(self.groupVis, 1, 0, 10, 1) + self.setLayout(layoutTab) + + def action_load_data(self): + + outputPath = pathlib.Path(self.lineOutput.text()) + self.telem = postproc_flight.load_mc(outputPath) + + # Populate metadata + metaStr = ", ".join(self.telem[0]["meta"]) + self.editMeta.setText(metaStr) + + # Update telemetry fields + self.listFields.clear() + fields = self.telem[0]["fields"] + units = self.telem[0]["units"] + iPop = fields.index("time") + fields.pop(iPop) + units.pop(iPop) + self.listFields.addItems(fields) + iRow = fields.index("linPosZ") + self.listFields.setCurrentRow(iRow) + + # Update spinbox + self.spinRunNum.setMaximum(len(self.telem)) + + # Initialize plot + self.action_plot_update() + + def action_plot_update(self): + + # TODO: plot arbitrary X vs Y data + + # Clear old data + self.plotTelem.clear() + + # Plot new data + field = self.listFields.currentItem().text() + iField = self.telem[0]["fields"].index(field) + units = self.telem[0]["units"][iField] + self.comboUnits.clear() + self.comboUnits.addItems([units]) + iRun = self.spinRunNum.value() - 1 + runAll = self.checkRunAll.isChecked() + + if runAll: + + nRun = len(self.telem) + plot = [None]*len(self.telem) + + for iRun in range(nRun): + + x = self.telem[iRun]["data"]["time"] + y = self.telem[iRun]["data"][field] + plot[iRun] = self.plotTelem.plot(x, y, pen=self.penTelem) + + else: + + x = self.telem[iRun]["data"]["time"] + y = self.telem[iRun]["data"][field] + plot = self.plotTelem.plot(x, y, pen=self.penTelem) + + self.plotTelem.setLabel("bottom", "Time", "s") + self.plotTelem.setLabel("left", field, units) + + # Reset axis limits + self.plotTelem.enableAutoRange() + + # Update plot axis limits + self.action_plot_get_range() + + def action_plot_update_units(self): + pass + + def action_plot_auto_range(self): + self.plotTelem.enableAutoRange() + + def action_plot_get_range(self): + + viewRange = self.plotTelem.getViewBox().viewRange() + + self.lineAxisXMin.setText(str(round(viewRange[0][0], 2))) + self.lineAxisXMax.setText(str(round(viewRange[0][1], 2))) + self.lineAxisYMin.setText(str(round(viewRange[1][0], 2))) + self.lineAxisYMax.setText(str(round(viewRange[1][1], 2))) + + def action_plot_set_range(self): + + xMin = float(self.lineAxisXMin.text()) + xMax = float(self.lineAxisXMax.text()) + + yMin = float(self.lineAxisYMin.text()) + yMax = float(self.lineAxisYMax.text()) + + self.plotTelem.setXRange(xMin, xMax) + self.plotTelem.setYRange(yMin, yMax) diff --git a/src/gui/gui_main.py b/src/gui/gui_main.py new file mode 100644 index 0000000..401053c --- /dev/null +++ b/src/gui/gui_main.py @@ -0,0 +1,26 @@ +# System modules +from PyQt5.QtWidgets import QApplication +from PyQt5.QtGui import QFont + +# Project modules +import util_misc +import gui_elements + +#------------------------------------------------------------------------------# + +def exec(): + + util_misc.qt_setup() + + app = QApplication([]) + font = QFont() + font.setPointSize(14) + app.setFont(font) + window = gui_elements.MainWindow() + window.show() + app.exec() + +#------------------------------------------------------------------------------# + +if __name__ == "__main__": + pass diff --git a/src/postproc/postproc_flight.py b/src/postproc/postproc_flight.py index 22179a9..7a6cdd0 100644 --- a/src/postproc/postproc_flight.py +++ b/src/postproc/postproc_flight.py @@ -2,14 +2,33 @@ import sys import numpy as np import matplotlib.pyplot as plt +import pathlib -#-----------------------------------------------------------------------------# +#------------------------------------------------------------------------------# -def postproc(filePath): +def load_mc(outputPath): + + subdirs = [subdir for subdir in outputPath.iterdir() if subdir.is_dir()] + telem = [None]*len(subdirs) + + for iRun, subdir in enumerate(subdirs): + + filePath = subdir / "telem.csv" + telem[iRun] = load_csv(filePath) + + return telem + +def load_csv(filePath): with open(filePath, 'r') as file: lines = file.read().splitlines() + # Remove comment lines in header + meta = [] + + while lines[0][0] == '#': + meta.append(lines.pop(0).strip("# ")) + fields = lines[0].split(',') units = lines[1].split(',') data = {} @@ -20,7 +39,7 @@ def postproc(filePath): data[field] = [] for line in lines[2:]: - + lineData = line.split(',') for iField in range(nField): @@ -29,33 +48,46 @@ def postproc(filePath): for field in fields: data[field] = np.array(data[field]) - ## BUILD PLOTS + telem = { + "meta" : meta , + "fields" : fields, + "units" : units , + "data" : data , + } - fields.remove("time") - units.remove("s") + return telem - # Set reasonable xlim - #iPos = np.where(data["linPosZ"] <= 0) - #iVel = np.where(data["linVelZ"] < 0) - #iGnd = np.intersect1d(iPos, iVel)[0] - #iGnd = iVel[0][-1] - iGnd = len(data["time"]) - 1 +# def postproc(filePath): - for field in fields: +# (data, fields, units) = process(filePath) + +# ## BUILD PLOTS + +# fields.remove("time") +# units.remove("s") + +# # Set reasonable xlim +# #iPos = np.where(data["linPosZ"] <= 0) +# #iVel = np.where(data["linVelZ"] < 0) +# #iGnd = np.intersect1d(iPos, iVel)[0] +# #iGnd = iVel[0][-1] +# iGnd = len(data["time"]) - 1 + +# for field in fields: - fig, ax = plt.subplots() +# fig, ax = plt.subplots() - ax.plot(data["time"][:iGnd+1], data[field][:iGnd+1]) +# ax.plot(data["time"][:iGnd+1], data[field][:iGnd+1]) - ax.set_title(field) - ax.set_xlabel("Time [s]") - #ax.set_ylabel(field + " [" + unit + ']') +# ax.set_title(field) +# ax.set_xlabel("Time [s]") +# #ax.set_ylabel(field + " [" + unit + ']') - ax.xaxis.grid() - ax.yaxis.grid() +# ax.xaxis.grid() +# ax.yaxis.grid() - ax.set_xlim(data["time"][0], data["time"][iGnd]) +# ax.set_xlim(data["time"][0], data["time"][iGnd]) - print("apogee = " + str(data["linPosZ"].max())) +# print("apogee = " + str(data["linPosZ"].max())) - plt.show() +# plt.show() diff --git a/src/util/util_misc.py b/src/util/util_misc.py index b84e8d9..011942d 100644 --- a/src/util/util_misc.py +++ b/src/util/util_misc.py @@ -41,6 +41,18 @@ def pybind11_setup(): #------------------------------------------------------------------------------# +def qt_setup(): + + if os.name == "posix": + + # Fix for interaction between Qt5 & Wayland + os.environ["QT_QPA_PLATFORM"] = "xcb" + + elif os.name == "nt": + pass + +#------------------------------------------------------------------------------# + if __name__ == "__main__": # Standalone execution diff --git a/tools/pyinstaller_build.py b/tools/pyinstaller_build.py index 4955aa5..9bd8c49 100644 --- a/tools/pyinstaller_build.py +++ b/tools/pyinstaller_build.py @@ -10,7 +10,7 @@ outputPath = distPath / "hpr-sim/output" # Set import search paths -paths = ["src/exec", "src/preproc", "src/postproc", "src/util"] +paths = ["src/exec", "src/gui", "src/preproc", "src/postproc", "src/util"] # Set binary files and bundled locations: ("filePath", "location") @@ -26,7 +26,7 @@ ("config/config_unit.yml" , "config")] # Excluded modules from bundle -excludes = ["PySide2"] # PySide2 conflicts with PyQt5 +excludes = ["PySide2", "PySide6", "PyQt6"] # Using PyQt5; Qt bindings conflict with each other # Execute PyInstaller commands PyInstaller.__main__.run([ @@ -44,6 +44,8 @@ paths[2], "--paths", paths[3], + "--paths", + paths[4], "--add-binary", f"{binaries[0][0]}:{binaries[0][1]}", "--add-data", @@ -55,7 +57,15 @@ "--add-data", f"{datas[3][0]}:{datas[3][1]}", "--exclude-module", - f"{excludes[0]}" + f"{excludes[0]}", + "--exclude-module", + f"{excludes[1]}", + "--exclude-module", + f"{excludes[2]}", + "--hidden-import", + "scipy.special._special_ufuncs", + "--hidden-import", + "scipy._lib.array_api_compat.numpy.fft" ]) outputPath.mkdir(parents=True, exist_ok=True)