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.

Application layout#

The gui_app.py script launches a graphical app using PySide6.

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

../../../_images/final_app_preprocessing.png

The Postprocessing tab shows the deformation plot.

../../../_images/final_app_postprocessing.png

Add a PyVista plotting frame to the window#

Start by importing the QtInteractor class from the pyvistaqt package and the MapdlTheme class from the ansys-mapdl-core package:

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

Then, add a plotter on the first tab:

def _setup_tab_preprocessing(self) -> None:
    ...
    # PyVista frame in the window
    self._preprocessing_plotter = QtInteractor(theme=MapdlTheme())
    container_layout.addWidget(self._preprocessing_plotter, 0, 4, 6, 50)

Add another plotter on the second tab:

def _setup_tab_postprocessing(self) -> None:
    ...
    self._postprocessing_plotter = QtInteractor(theme=MapdlTheme())
    container_layout.addWidget(self._postprocessing_plotter)

The plotter can be updated with a PyMAPDL plotter object as follow:

# Getting PyMAPDL plotter object
nodal_disp_plotter = self._mapdl.post_processing.plot_nodal_displacement(
    "norm", show_node_numbering=True, cpos="xy", return_plotter=True
)

# Updating widget
self._postprocessing_plotter.GetRenderWindow().AddRenderer(
    nodal_disp_plotter.scene.renderer
)

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

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#

In this example, the MAPDL instance is launched outside the MainWindow object, and it passed to it as an argument.

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

The MainWindow object stores the Mapdl object internally:

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

Simulation setup#

The model is built in build_model method:

    def build_model(self, poisson_ratio, young_modulus, nnodes, length, force):
        self._mapdl.clear()
        self._mapdl.verify()
        self._mapdl.prep7()
        self._mapdl.antype("STATIC")
        # create element type
        self._mapdl.et(1, "BEAM188")

        # Create material
        self._mapdl.mp("PRXY", 1, poisson_ratio)
        self._mapdl.mp("EX", 1, young_modulus)
        self._mapdl.sectype(1, "BEAM", "RECT")
        self._mapdl.secdata("10", "10")

        # Create the nodes
        for node_num in range(1, nnodes + 1):
            self._mapdl.n(node_num, (node_num - 1) * length / (nnodes - 1), 0, 0)

        # Create the elements
        for elem_num in range(1, nnodes):
            self._mapdl.e(elem_num, elem_num + 1)

        # Fix beam ends
        self._mapdl.d(1, lab="ALL")
        self._mapdl.d(nnodes, lab="ALL")

        #  Apply the force to the node in the middle
        self._mapdl.f(nnodes // 2 + 1, lab="FY", value=force)

And solved in run_solver:

    def run_solver(self) -> None:
        # solve
        self._mapdl.slashsolu()
        self._mapdl.solve()
        self._mapdl.finish()

        # run postprocessing
        self._mapdl.post1()
        self._mapdl.graphics("power")
        self._mapdl.eshape(1)
        self._mapdl.rgb("index", 100, 100, 100, 0)
        self._mapdl.rgb("index", 0, 0, 0, 15)

        mapdl.upcoord(2)

        nodal_disp_plotter = self._mapdl.post_processing.plot_nodal_displacement(
            "norm", show_node_numbering=True, cpos="xy", return_plotter=True
        )
        self._postprocessing_plotter.GetRenderWindow().AddRenderer(
            nodal_disp_plotter.scene.renderer
        )

        nnodes = len(mapdl.mesh.nodes)

        mid_node_uy = mapdl.get_value(
            entity="NODE", entnum=nnodes // 2 + 1, item1="u", it1num="y"
        )

        self._deflection_label.setText(f"Deflection: {mid_node_uy:.6f}")

Develop the logic#

Connect each button to a function that contains the logic:

def _setup_tab_preprocessing(self) -> None:
    ...
    # Solve button
    self._solve_button = QPushButton(text="Solve")
    self._solve_button.clicked.connect(self.run_solver)
    container_layout.addWidget(self._solve_button, 5, 0, 1, 3)
    ...

Run the app#

You can run the app as a normal python script:

$ python gui_app.py

Additional files#

The example files can be downloaded using this link: