Building a model from a pyvista grid#

When you don’t have a CDB on disk, the easiest way to build a femorph_solver.Model from scratch is to author a pyvista.UnstructuredGrid directly and hand it to femorph_solver.Model.from_grid(). Here we build a 4-element HEX8 cantilever beam this way — no foreign deck required.

from __future__ import annotations

import numpy as np
import pyvista as pv
from vtkmodules.util.vtkConstants import VTK_HEXAHEDRON

import femorph_solver
from femorph_solver import ELEMENTS

Author the nodes#

LX, LY, LZ = 4.0, 1.0, 1.0
NX = 4

points: list[tuple[float, float, float]] = []
node_id: dict[tuple[int, int, int], int] = {}
nn = 0
for i in range(NX + 1):
    for j in range(2):
        for k in range(2):
            points.append((i * LX / NX, j * LY, k * LZ))
            node_id[(i, j, k)] = nn  # 0-based pyvista index
            nn += 1

points_arr = np.asarray(points, dtype=float)

Author the hex connectivity#

cells: list[int] = []
for i in range(NX):
    cells.extend(
        [
            8,
            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)],
        ]
    )

cell_types = np.full(NX, VTK_HEXAHEDRON, dtype=np.uint8)
grid = pv.UnstructuredGrid(np.asarray(cells), cell_types, points_arr)

Wrap as a Model and stamp material#

m = femorph_solver.Model.from_grid(grid)
m.assign(ELEMENTS.HEX8, material={"EX": 2.1e11, "PRXY": 0.30, "DENS": 7850.0})

print(f"model: {m.n_nodes} nodes, {m.n_elements} elements")
model: 20 nodes, 4 elements

Apply BCs and solve#

node_nums = np.asarray(grid.point_data["ansys_node_num"])
pts = np.asarray(grid.points)

fixed = node_nums[pts[:, 0] < 1e-9]
m.fix(nodes=fixed.tolist(), dof="ALL")

tip_mask = pts[:, 0] > LX - 1e-9
tip_nodes = node_nums[tip_mask]
for tip_nn in tip_nodes:
    m.apply_force(int(tip_nn), fz=-250.0)

res = m.solve()
dof_map = m.dof_map()
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")
tip UZ (mean over 4 corner nodes): -1.4761e-06 m

Render#

deformed = femorph_solver.io.static_result_to_grid(m, res)
plotter = pv.Plotter(off_screen=True)
plotter.add_mesh(deformed, style="wireframe", color="gray", opacity=0.35, label="undeformed")
plotter.add_mesh(
    deformed.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 1.112 seconds)

Gallery generated by Sphinx-Gallery