@@ -6,163 +6,27 @@ Create a GUI app in Python with PySide6
66
77This 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
16326Start 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
217122Develop 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.
0 commit comments