Skip to content

Commit dff1bab

Browse files
authored
fix: gui-extended-example (#3555)
* fix: code blocks * fix: gui example * refactor: move build the model to a function * docs: reorg example * fix: typos * feat: improving example * feat: adding requirements
1 parent 099dc79 commit dff1bab

File tree

8 files changed

+189
-234
lines changed

8 files changed

+189
-234
lines changed

doc/changelog.d/3555.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fix: gui-extended-example
-8.38 KB
Binary file not shown.

doc/source/examples/extended_examples/gui/executable.rst

Lines changed: 73 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -6,163 +6,27 @@ Create a GUI app in Python with PySide6
66

77
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.
88

9-
Simulation setup
10-
================
11-
12-
The following script launches a graphical app using PySide6:
13-
14-
.. code-block:: python
15-
16-
import sys
17-
18-
from PySide6 import QtWidgets
19-
from PySide6.QtCore import Qt
20-
from PySide6.QtWidgets import (
21-
QApplication,
22-
QGridLayout,
23-
QLabel,
24-
QLineEdit,
25-
QMainWindow,
26-
QPushButton,
27-
QSlider,
28-
QVBoxLayout,
29-
QWidget,
30-
)
31-
9+
Application layout
10+
==================
3211

33-
class MainWindow(QMainWindow):
34-
def __init__(self, parent=None) -> None:
35-
super().__init__(parent)
36-
self._setup_ui()
37-
38-
def _setup_ui(self) -> None:
39-
# General settings for the window
40-
self.setWindowTitle("PyMAPDL example application")
41-
self.resize(1000, 500)
42-
self._widget = QWidget()
43-
self._layout = QVBoxLayout()
44-
self._layout.setContentsMargins(0, 0, 0, 0)
45-
46-
# Create the tabs
47-
self._tab_widget = QtWidgets.QTabWidget()
48-
49-
self._tab_preprocessing = QtWidgets.QWidget()
50-
self._tab_widget.addTab(self._tab_preprocessing, "Preprocessing")
51-
self._setup_tab_preprocessing()
52-
53-
self._tab_solver = QtWidgets.QWidget()
54-
self._tab_widget.addTab(self._tab_solver, "Solver")
55-
self._setup_tab_solver()
56-
57-
self._tab_postprocessing = QtWidgets.QWidget()
58-
self._tab_widget.addTab(self._tab_postprocessing, "Postprocessing")
59-
self._setup_tab_postprocessing()
60-
61-
self._layout.addWidget(self._tab_widget)
62-
self._widget.setLayout(self._layout)
63-
self.setCentralWidget(self._widget)
64-
65-
def _setup_tab_preprocessing(self) -> None:
66-
container_layout = QGridLayout()
67-
max_qlineedit_width = 250
68-
self._tab_preprocessing.setLayout(container_layout)
69-
70-
# Poisson's ration input
71-
poisson_ratio_label = QLabel("Poisson's ratio: ")
72-
container_layout.addWidget(poisson_ratio_label, 0, 0)
73-
self._poisson_ratio_input = QLineEdit()
74-
self._poisson_ratio_input.setPlaceholderText("Poisson's ratio (PRXY)")
75-
self._poisson_ratio_input.setText("0.3")
76-
self._poisson_ratio_input.setMaximumWidth(max_qlineedit_width)
77-
78-
# Young modulus input
79-
young_modulus_label = QLabel("Young's modulus: ")
80-
container_layout.addWidget(young_modulus_label, 1, 0)
81-
self._young_modulus_input = QLineEdit()
82-
self._young_modulus_input.setPlaceholderText(
83-
"Young's modulus in the x direction"
84-
)
85-
self._young_modulus_input.setText("210e3")
86-
self._young_modulus_input.setMaximumWidth(max_qlineedit_width)
87-
88-
# beam length input
89-
length_label = QLabel("Length: ")
90-
container_layout.addWidget(length_label, 2, 0)
91-
self._length_input = QLineEdit()
92-
self._length_input.setPlaceholderText("Length")
93-
self._length_input.setMaximumWidth(max_qlineedit_width)
94-
95-
# Input for the force exerced on the beam
96-
force_label = QLabel("Force: ")
97-
container_layout.addWidget(force_label, 3, 0)
98-
self._force_input = QLineEdit()
99-
self._force_input.setPlaceholderText("Load force")
100-
self._force_input.setMaximumWidth(max_qlineedit_width)
101-
102-
# Slider for the number of nodes (between 3 and 9)
103-
number_of_nodes_label = QLabel("Number of nodes: ")
104-
container_layout.addWidget(number_of_nodes_label, 4, 0)
105-
self._number_of_nodes_input = QSlider(orientation=Qt.Orientation.Horizontal)
106-
self._number_of_nodes_input.setMinimum(3)
107-
self._number_of_nodes_input.setMaximum(9)
108-
self._number_of_nodes_input.setValue(5)
109-
self._number_of_nodes_input.setSingleStep(2)
110-
self._number_of_nodes_input.setPageStep(2)
111-
self._number_of_nodes_input.setMaximumWidth(max_qlineedit_width - 50)
112-
self._number_of_node_label = QLabel(
113-
f"{self._number_of_nodes_input.value()} nodes"
114-
)
115-
self._number_of_nodes_input.valueChanged.connect(
116-
lambda _: self._number_of_node_label.setText(
117-
f"{self._number_of_nodes_input.value()} nodes"
118-
)
119-
)
120-
121-
# Button to run the preprocessor
122-
self._run_preprocessor_button = QPushButton(text="Run preprocessor")
123-
124-
container_layout.addWidget(self._poisson_ratio_input, 0, 1, 1, 2)
125-
container_layout.addWidget(self._young_modulus_input, 1, 1, 1, 2)
126-
container_layout.addWidget(self._length_input, 2, 1, 1, 2)
127-
container_layout.addWidget(self._force_input, 3, 1, 1, 2)
128-
container_layout.addWidget(self._number_of_nodes_input, 4, 1, 1, 1)
129-
container_layout.addWidget(self._number_of_node_label, 4, 2, 1, 1)
130-
container_layout.addWidget(self._run_preprocessor_button, 5, 0, 1, 3)
131-
132-
def _setup_tab_solver(self) -> None:
133-
container_layout = QGridLayout()
134-
self._tab_solver.setLayout(container_layout)
135-
136-
# Button to run the solver
137-
self._solve_button = QPushButton(text="Solve")
138-
139-
container_layout.addWidget(self._solve_button)
140-
141-
def _setup_tab_postprocessing(self) -> None:
142-
container_layout = QtWidgets.QVBoxLayout()
143-
self._tab_postprocessing.setLayout(container_layout)
144-
self._deflection_label = QLabel("Deflection: ")
145-
container_layout.addWidget(self._deflection_label)
12+
The :download:`gui_app.py <gui_app.py>` script launches a graphical app using PySide6.
14613

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

148-
if __name__ == "__main__":
149-
app = QApplication(sys.argv)
150-
window = MainWindow()
151-
window.show()
152-
sys.exit(app.exec())
153-
16+
.. image:: final_app_preprocessing.png
15417

155-
The **Preprocessing** tab contains input fields for Poisson's ratio, Young modulus, beam length, and a number of simulation nodes.
18+
The **Postprocessing** tab shows the deformation plot.
15619

20+
.. image:: final_app_postprocessing.png
15721

158-
.. image:: base_app.png
15922

160-
Add a PyVista plotting frame in the window
23+
Add a PyVista plotting frame to the window
16124
==========================================
16225

16326
Start by importing the `QtInteractor <https://qtdocs.pyvista.org/api_reference.html#qtinteractor>`_
164-
class from the ``pyvistaqt`` package and the :class:`MapdlTheme <ansys.mapdl.core.plotting.theme.MapdlTheme>`
165-
class from the ``ansys-mapdl-core`` package:
27+
class from the `pyvistaqt <https://github.com/pyvista/pyvistaqt>`_ package and
28+
the :class:`MapdlTheme <ansys.mapdl.core.plotting.theme.MapdlTheme>`
29+
class from the `ansys-mapdl-core <pymapdl_repo_>`_ package:
16630

16731
.. code:: python
16832
@@ -177,8 +41,6 @@ Then, add a plotter on the first tab:
17741
17842
def _setup_tab_preprocessing(self) -> None:
17943
...
180-
container_layout.addWidget(self._run_preprocessor_button, 5, 0, 1, 3)
181-
18244
# PyVista frame in the window
18345
self._preprocessing_plotter = QtInteractor(theme=MapdlTheme())
18446
container_layout.addWidget(self._preprocessing_plotter, 0, 4, 6, 50)
@@ -190,12 +52,23 @@ Add another plotter on the second tab:
19052
.. code:: python
19153
19254
def _setup_tab_postprocessing(self) -> None:
193-
container_layout = QtWidgets.QVBoxLayout()
194-
self._tab_postprocessing.setLayout(container_layout)
55+
...
19556
self._postprocessing_plotter = QtInteractor(theme=MapdlTheme())
19657
container_layout.addWidget(self._postprocessing_plotter)
197-
self._deflection_label = QLabel("Deflection: ")
198-
container_layout.addWidget(self._deflection_label)
58+
59+
The plotter can be updated with a PyMAPDL plotter object as follow:
60+
61+
.. code:: python
62+
63+
# Getting PyMAPDL plotter object
64+
nodal_disp_plotter = self._mapdl.post_processing.plot_nodal_displacement(
65+
"norm", show_node_numbering=True, cpos="xy", return_plotter=True
66+
)
67+
68+
# Updating widget
69+
self._postprocessing_plotter.GetRenderWindow().AddRenderer(
70+
nodal_disp_plotter.scene.renderer
71+
)
19972
20073
Finally, make sure to correctly close the VTK widgets when closing the app:
20174

@@ -209,10 +82,42 @@ Finally, make sure to correctly close the VTK widgets when closing the app:
20982
Launch an MAPDL instance in your window
21083
=======================================
21184

212-
Add an attribute to your MainWindow for the MAPDL instance and import the ``launch_mapdl`` package.
85+
In this example, the MAPDL instance is launched outside the ``MainWindow`` object,
86+
and it passed to it as an argument.
87+
88+
.. code:: python
89+
90+
if __name__ == "__main__":
91+
app = QApplication(sys.argv)
92+
mapdl = launch_mapdl()
93+
window = MainWindow(mapdl)
94+
window.show()
95+
sys.exit(app.exec())
96+
97+
The ``MainWindow`` object stores the :class:`Mapdl <ansys.mapdl.core.mapdl.MapdlBase>` object internally:
98+
99+
.. code:: python
100+
101+
class MainWindow(QMainWindow):
102+
def __init__(self, mapdl: Mapdl, parent=None) -> None:
103+
super().__init__(parent)
104+
self._mapdl = mapdl
105+
self._setup_ui()
106+
107+
108+
Simulation setup
109+
================
110+
111+
The model is built in ``build_model`` method:
112+
113+
.. literalinclude:: gui_app.py
114+
:lines: 189-216
115+
116+
And solved in ``run_solver``:
213117

214118
.. literalinclude:: gui_app.py
215-
:lines: 19, 22-26, 231-236
119+
:lines: 218-246
120+
216121

217122
Develop the logic
218123
=================
@@ -222,41 +127,32 @@ Connect each button to a function that contains the logic:
222127
.. vale off
223128
224129
.. code-block:: python
225-
:emphasize-lines: 5,14
130+
:emphasize-lines: 5
226131
227132
def _setup_tab_preprocessing(self) -> None:
228133
...
229-
# Button to run the preprocessor
230-
self._run_preprocessor_button = QPushButton(text="Run preprocessor")
231-
self._run_preprocessor_button.clicked.connect(self._run_preprocessor)
232-
...
233-
234-
235-
def _setup_tab_solver(self) -> None:
236-
container_layout = QGridLayout()
237-
self._tab_solver.setLayout(container_layout)
238-
134+
# Solve button
239135
self._solve_button = QPushButton(text="Solve")
240-
self._solve_button.clicked.connect(self._run_solver)
241-
242-
container_layout.addWidget(self._solve_button)
136+
self._solve_button.clicked.connect(self.run_solver)
137+
container_layout.addWidget(self._solve_button, 5, 0, 1, 3)
138+
...
243139
244140
.. vale on
245141
246-
You can now write the related functions:
142+
Run the app
143+
===========
247144

248-
.. literalinclude:: gui_app.py
249-
:lines: 137-223
145+
You can run the app as a normal python script:
250146

251-
.. image:: final_app_preprocessing.png
147+
.. code:: console
252148
253-
.. image:: final_app_solver.png
149+
$ python gui_app.py
254150
255-
.. image:: final_app_postprocessing.png
256151
257152
Additional files
258153
================
259154

260-
The example files can be downloaded using these links:
155+
The example files can be downloaded using this link:
261156

262-
* Original :download:`gui_app.py <gui_app.py>` script
157+
* :download:`gui_app.py <gui_app.py>`: Complete Python script.
158+
* :download:`requirements.txt <requirements.txt>`: Python libraries requirements.
42.9 KB
Loading
52.7 KB
Loading
-7.16 KB
Binary file not shown.

0 commit comments

Comments
 (0)