Building a model with MAPDL-style verbs#

When you don’t have a CDB and don’t want to build a pyvista mesh from scratch, the femorph_solver.Model exposes a set of verbs that mirror MAPDL’s /PREP7 commands one-to-one: n for nodes, e for elements, et / mp / r for the type / material / real tables. Here we build a 4-element cantilever beam out of SOLID185 hexes entirely with the verb API — no pyvista, no CDB.

from __future__ import annotations

import numpy as np
import pyvista as pv

import femorph_solver

Start from an empty Model#

m = femorph_solver.Model()
m.et(1, "SOLID185")
m.mp("EX", 1, 2.1e11)
m.mp("PRXY", 1, 0.30)
m.mp("DENS", 1, 7850.0)
m.type(1)
m.mat(1)
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:24: DeprecationWarning: Model.et(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).et(et_id, name)` for line-by-line APDL deck porting, or the native `Model.assign("HEX8", material)` for new code.
  m.et(1, "SOLID185")
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:25: DeprecationWarning: Model.mp(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).mp(prop, mat_id, value)` for line-by-line APDL deck porting, or the native `Model.assign(element, {prop: value, ...})` for new code.
  m.mp("EX", 1, 2.1e11)
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:26: DeprecationWarning: Model.mp(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).mp(prop, mat_id, value)` for line-by-line APDL deck porting, or the native `Model.assign(element, {prop: value, ...})` for new code.
  m.mp("PRXY", 1, 0.30)
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:27: DeprecationWarning: Model.mp(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).mp(prop, mat_id, value)` for line-by-line APDL deck porting, or the native `Model.assign(element, {prop: value, ...})` for new code.
  m.mp("DENS", 1, 7850.0)
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:28: DeprecationWarning: Model.type(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).type(et_id)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.type(1)
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:29: DeprecationWarning: Model.mat(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).mat(mat_id)` for line-by-line APDL deck porting, or the native `Model.assign("HEX8", material, mat_id=...)` for new code.
  m.mat(1)

Add nodes with n()#

LX, LY, LZ = 4.0, 1.0, 1.0
NX = 4
nn = 1
node_id: dict[tuple[int, int, int], int] = {}
for i in range(NX + 1):
    for j in range(2):
        for k in range(2):
            m.n(nn, i * LX / NX, j * LY, k * LZ)
            node_id[(i, j, k)] = nn
            nn += 1
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:41: DeprecationWarning: Model.n(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).n(num, x, y, z)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.n(nn, i * LX / NX, j * LY, k * LZ)

Add elements with e()#

for i in range(NX):
    m.e(
        node_id[(i, 0, 0)],
        node_id[(i + 1, 0, 0)],
        node_id[(i + 1, 1, 0)],
        node_id[(i, 1, 0)],
        node_id[(i, 0, 1)],
        node_id[(i + 1, 0, 1)],
        node_id[(i + 1, 1, 1)],
        node_id[(i, 1, 1)],
    )

print(f"model: {m.n_nodes} nodes, {m.n_elements} elements")
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:49: DeprecationWarning: Model.e(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).e(*node_nums)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.e(
model: 20 nodes, 4 elements

Apply BCs and solve#

for j in range(2):
    for k in range(2):
        m.d(node_id[(0, j, k)], "ALL")

for j in range(2):
    for k in range(2):
        m.f(node_id[(NX, j, k)], "FZ", -250.0)

res = m.solve()
dof_map = m.dof_map()
tip_nodes = [node_id[(NX, j, k)] for j in range(2) for k in range(2)]
tip_uz = np.array(
    [
        res.displacement[int(np.where((dof_map[:, 0] == nn) & (dof_map[:, 1] == 2))[0][0])]
        for nn in tip_nodes
    ]
)
print(f"tip UZ (mean over 4 corner nodes): {tip_uz.mean():.4e} m")
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:67: DeprecationWarning: Model.d(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).d(node, label, value)` for line-by-line APDL deck porting, or the native `Model.fix(nodes=..., where=..., dof=...)` for new code.
  m.d(node_id[(0, j, k)], "ALL")
/home/runner/_work/solver/solver/examples/pre-processing/example_build_mesh.py:71: DeprecationWarning: Model.f(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).f(node, label, value)` for line-by-line APDL deck porting, or the native `Model.apply_force(node, fx=..., fy=..., fz=...)` for new code.
  m.f(node_id[(NX, j, k)], "FZ", -250.0)
tip UZ (mean over 4 corner nodes): -1.4761e-06 m

Render#

grid = femorph_solver.io.static_result_to_grid(m, res)
plotter = pv.Plotter(off_screen=True)
plotter.add_mesh(grid, style="wireframe", color="gray", opacity=0.35, label="undeformed")
plotter.add_mesh(
    grid.warp_by_vector("displacement", factor=500.0),
    scalars="displacement_magnitude",
    show_edges=True,
    cmap="viridis",
    scalar_bar_args={"title": "|u| [m]"},
    label="deformed (×500)",
)
plotter.add_legend()
plotter.add_axes()
plotter.show()
example build mesh

Total running time of the script: (0 minutes 0.350 seconds)

Gallery generated by Sphinx-Gallery