Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create an example of a GUI application with PySide6 #2095

Merged
merged 33 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7a2a874
add cpxmod field to eigs function in math module
Gryfenfer97 May 25, 2023
4b642bc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 25, 2023
73a1407
Merge branch 'main' into fix/cpxmod-field-for-eigs
germa89 May 30, 2023
81e8a6e
Merge branch 'main' into fix/cpxmod-field-for-eigs
clatapie May 30, 2023
568d331
pass default value to MAPDL if cpxmod is not set
Gryfenfer97 May 31, 2023
fae50f4
Merge branch 'fix/cpxmod-field-for-eigs' of https://github.com/ansys/…
Gryfenfer97 May 31, 2023
0bd32b3
add example to create a gui app with pyside
Gryfenfer97 Jun 1, 2023
39d0b78
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 1, 2023
3c20023
fix mistakes in documentation
Gryfenfer97 Jun 2, 2023
5661b04
Merge branch 'example/gui' of https://github.com/ansys/pymapdl into e…
Gryfenfer97 Jun 2, 2023
1535bd8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 2, 2023
f174f80
fix vocabulary issues
Gryfenfer97 Jun 2, 2023
a5009bd
fix poisson word
Gryfenfer97 Jun 2, 2023
aeda12d
fix start-line and add importing mapdl section in MainWindow
Gryfenfer97 Jun 2, 2023
3d1c21f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 2, 2023
bc573a1
add gui in sidebar
Gryfenfer97 Jun 2, 2023
e701dc4
Merge branch 'main' into example/gui
germa89 Jun 2, 2023
5de5fb9
add screenshots to gui example
Gryfenfer97 Jun 2, 2023
b2bb810
Apply suggestions from code review
Gryfenfer97 Jun 5, 2023
47a775f
add solver image
Gryfenfer97 Jun 5, 2023
5764dfb
replace application with app
Gryfenfer97 Jun 5, 2023
c21ef95
change size of image at the end of gui example
Gryfenfer97 Jun 5, 2023
67c1aae
Merge branch 'main' into example/gui
clatapie Jun 7, 2023
aa7d12c
Merge branch 'main' into example/gui
germa89 Jun 12, 2023
e8890cd
Apply suggestions from code review
Gryfenfer97 Jun 12, 2023
3e6fa31
Adding MapdlTheme to toctree so we can reference it.
germa89 Jun 13, 2023
c9557be
improve doc following code review suggestions
Gryfenfer97 Jun 13, 2023
a9bfaa4
fix vale errors
Gryfenfer97 Jun 13, 2023
fbbee3e
Apply suggestions from code review
Gryfenfer97 Jun 14, 2023
b77ce66
Apply suggestions from code review
Gryfenfer97 Jun 14, 2023
49e49b5
Apply suggestions from code review
Gryfenfer97 Jun 14, 2023
264c236
remove gui.py
Gryfenfer97 Jun 14, 2023
3ad8f6a
Merge branch 'main' into example/gui
Gryfenfer97 Jun 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ tests/cyclic/htmlcov
.benchmarks/
test-output.xml
coverage.xml
.tox/

\#*
.\#*
Expand Down
1 change: 1 addition & 0 deletions doc/source/api/plotting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Various PyMAPDL specific plotting commands.
:toctree: _autosummary

plotting.general_plotter
plotting.MapdlTheme
Mapdl.aplot
Mapdl.eplot
Mapdl.kplot
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
252 changes: 252 additions & 0 deletions doc/source/examples/extended_examples/gui/executable.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
.. _gui_example:

=======================================
Create a GUI app in Python with PySide6
=======================================

This example shows how to create a graphical user interface (GUI) app in Python that uses PyMAPDL to compute the deflection of a square beam.

Simulation setup
================

The following script launches a graphical app using PySide6:

.. code-block:: python

import sys

from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QApplication,
QGridLayout,
QLabel,
QLineEdit,
QMainWindow,
QPushButton,
QSlider,
QVBoxLayout,
QWidget,
)


class MainWindow(QMainWindow):
def __init__(self, parent=None) -> None:
super().__init__(parent)
self._setup_ui()

def _setup_ui(self) -> None:
# General settings for the window
self.setWindowTitle("PyMAPDL example application")
self.resize(1000, 500)
self._widget = QWidget()
self._layout = QVBoxLayout()
self._layout.setContentsMargins(0, 0, 0, 0)

# Create the tabs
self._tab_widget = QtWidgets.QTabWidget()

self._tab_preprocessing = QtWidgets.QWidget()
self._tab_widget.addTab(self._tab_preprocessing, "Preprocessing")
self._setup_tab_preprocessing()

self._tab_solver = QtWidgets.QWidget()
self._tab_widget.addTab(self._tab_solver, "Solver")
self._setup_tab_solver()

self._tab_postprocessing = QtWidgets.QWidget()
self._tab_widget.addTab(self._tab_postprocessing, "Postprocessing")
self._setup_tab_postprocessing()

self._layout.addWidget(self._tab_widget)
self._widget.setLayout(self._layout)
self.setCentralWidget(self._widget)

def _setup_tab_preprocessing(self) -> None:
container_layout = QGridLayout()
max_qlineedit_width = 250
self._tab_preprocessing.setLayout(container_layout)

# Poisson's ration input
poisson_ratio_label = QLabel("Poisson's ratio: ")
container_layout.addWidget(poisson_ratio_label, 0, 0)
self._poisson_ratio_input = QLineEdit()
self._poisson_ratio_input.setPlaceholderText("Poisson's ratio (PRXY)")
self._poisson_ratio_input.setText("0.3")
self._poisson_ratio_input.setMaximumWidth(max_qlineedit_width)

# Young modulus input
young_modulus_label = QLabel("Young's modulus: ")
container_layout.addWidget(young_modulus_label, 1, 0)
self._young_modulus_input = QLineEdit()
self._young_modulus_input.setPlaceholderText(
"Young's modulus in the x direction"
)
self._young_modulus_input.setText("210e3")
self._young_modulus_input.setMaximumWidth(max_qlineedit_width)

# beam length input
length_label = QLabel("Length: ")
container_layout.addWidget(length_label, 2, 0)
self._length_input = QLineEdit()
self._length_input.setPlaceholderText("Length")
self._length_input.setMaximumWidth(max_qlineedit_width)

# Input for the force exerced on the beam
force_label = QLabel("Force: ")
container_layout.addWidget(force_label, 3, 0)
self._force_input = QLineEdit()
self._force_input.setPlaceholderText("Load force")
self._force_input.setMaximumWidth(max_qlineedit_width)

# Slider for the number of nodes (between 3 and 9)
number_of_nodes_label = QLabel("Number of nodes: ")
container_layout.addWidget(number_of_nodes_label, 4, 0)
self._number_of_nodes_input = QSlider(orientation=Qt.Orientation.Horizontal)
self._number_of_nodes_input.setMinimum(3)
self._number_of_nodes_input.setMaximum(9)
self._number_of_nodes_input.setValue(5)
self._number_of_nodes_input.setSingleStep(2)
self._number_of_nodes_input.setPageStep(2)
self._number_of_nodes_input.setMaximumWidth(max_qlineedit_width - 50)
self._number_of_node_label = QLabel(
f"{self._number_of_nodes_input.value()} nodes"
)
self._number_of_nodes_input.valueChanged.connect(
lambda _: self._number_of_node_label.setText(
f"{self._number_of_nodes_input.value()} nodes"
)
)

# Button to run the preprocessor
self._run_preprocessor_button = QPushButton(text="Run preprocessor")

container_layout.addWidget(self._poisson_ratio_input, 0, 1, 1, 2)
container_layout.addWidget(self._young_modulus_input, 1, 1, 1, 2)
container_layout.addWidget(self._length_input, 2, 1, 1, 2)
container_layout.addWidget(self._force_input, 3, 1, 1, 2)
container_layout.addWidget(self._number_of_nodes_input, 4, 1, 1, 1)
container_layout.addWidget(self._number_of_node_label, 4, 2, 1, 1)
container_layout.addWidget(self._run_preprocessor_button, 5, 0, 1, 3)

def _setup_tab_solver(self) -> None:
container_layout = QGridLayout()
self._tab_solver.setLayout(container_layout)

# Button to run the solver
self._solve_button = QPushButton(text="Solve")

container_layout.addWidget(self._solve_button)

def _setup_tab_postprocessing(self) -> None:
container_layout = QtWidgets.QVBoxLayout()
self._tab_postprocessing.setLayout(container_layout)
self._deflection_label = QLabel("Deflection: ")
container_layout.addWidget(self._deflection_label)


if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())


The **Preprocessing** tab contains input fields for Poisson's ratio, Young modulus, beam length, and a number of simulation nodes.


.. image:: base_app.png

Add a PyVista plotting frame in the window
==========================================

Start by importing the `QtInteractor <https://qtdocs.pyvista.org/api_reference.html#qtinteractor>`_ class from the ``pyvistaqt`` package and the :class:`MapdlTheme <ansys.mapdl.core.theme.MapdlTheme>` class from the ``ansys-mapdl-core`` package:

.. code:: python

from pyvistaqt import QtInteractor
from ansys.mapdl.core import MapdlTheme

Then, add a plotter on the first tab:

.. code:: python

def _setup_tab_preprocessing(self) -> None:
...
container_layout.addWidget(self._run_preprocessor_button, 5, 0, 1, 3)

# PyVista frame in the window
self._preprocessing_plotter = QtInteractor(theme=MapdlTheme())
container_layout.addWidget(self._preprocessing_plotter, 0, 4, 6, 50)
Gryfenfer97 marked this conversation as resolved.
Show resolved Hide resolved

Add another plotter on the second tab:

.. code:: python

def _setup_tab_postprocessing(self) -> None:
container_layout = QtWidgets.QVBoxLayout()
self._tab_postprocessing.setLayout(container_layout)
self._postprocessing_plotter = QtInteractor(theme=MapdlTheme())
container_layout.addWidget(self._postprocessing_plotter)
self._deflection_label = QLabel("Deflection: ")
container_layout.addWidget(self._deflection_label)

Finally, make sure to correctly close the VTK widgets when closing the app:

.. code:: python

def closeEvent(self, event) -> None:
self._preprocessing_plotter.close()
self._postprocessing_plotter.close()
event.accept() # let the window close

Launch an MAPDL instance in your window
=======================================

Add an attribute to your MainWindow for the MAPDL instance and import the ``launch_mapdl`` package.

.. literalinclude:: gui_app.py
:lines: 19, 22-26, 231-236

Develop the logic
=================

Connect each button to a function that contains the logic:

.. code-block:: python
:emphasize-lines: 5,14

def _setup_tab_preprocessing(self) -> None:
...
# Button to run the preprocessor
self._run_preprocessor_button = QPushButton(text="Run preprocessor")
self._run_preprocessor_button.clicked.connect(self._run_preprocessor)
...


def _setup_tab_solver(self) -> None:
container_layout = QGridLayout()
self._tab_solver.setLayout(container_layout)

self._solve_button = QPushButton(text="Solve")
self._solve_button.clicked.connect(self._run_solver)

container_layout.addWidget(self._solve_button)

You can now write the related functions:

.. literalinclude:: gui_app.py
:lines: 137-223

.. image:: final_app_preprocessing.png

Gryfenfer97 marked this conversation as resolved.
Show resolved Hide resolved
.. image:: final_app_solver.png

.. image:: final_app_postprocessing.png

Additional files
================

The example files can be downloaded using these links:

* Original :download:`gui_app.py <gui_app.py>` script
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading