Topological tools
import numpy as np
import pyvista as pv
import mefikit as mf
pv.set_plot_theme("dark")
pv.set_jupyter_backend("static")
Submesh functionality
x = range(3)
y = np.linspace(0.0, 3.0, 5, endpoint=True)
z = np.logspace(-0.5, 1, 4, endpoint=True)
volumes = mf.build_cmesh(x, y, z)
Simple descending_mesh
This functionality is able to compute the descending connectivity of the provided mesh. It can act on elements of dimension 1, 2 or 3.
faces = volumes.descend()
edges = faces.descend()
vertex = edges.descend()
plotter = pv.Plotter(shape=(1, 3))
plotter.subplot(0, 0)
plotter.add_mesh(faces.to_pyvista().shrink(0.8), show_edges=True)
plotter.subplot(0, 1)
plotter.add_mesh(edges.to_pyvista().shrink(0.8))
plotter.subplot(0, 2)
plotter.add_mesh(vertex.to_pyvista())
plotter.show()
Submesh in one go
You might want to directly access either the node mesh or the edges mesh. You can ! And going into one step is ever faster than chaining multiple .descend() calls.
edges = volumes.descend(target_dim=1)
vertex = volumes.descend(target_dim=0)
plotter = pv.Plotter(shape=(1, 2))
plotter.subplot(0, 0)
plotter.add_mesh(edges.to_pyvista().shrink(0.8))
plotter.subplot(0, 1)
plotter.add_mesh(vertex.to_pyvista())
plotter.show()
Boundaries computation
As it is very common to compute boundaries on a mesh (for boundary conditions for ex), there is a custom boundaries computation method.
face_bounds = volumes.boundaries()
edge_bounds = volumes.boundaries(target_dim=1)
vertex_bounds = volumes.boundaries(target_dim=0)
plotter = pv.Plotter(shape=(1, 3))
plotter.subplot(0, 0)
plotter.add_mesh(face_bounds.to_pyvista().shrink(0.8), show_edges=True)
plotter.subplot(0, 1)
plotter.add_mesh(edge_bounds.to_pyvista().shrink(0.8))
plotter.subplot(0, 2)
plotter.add_mesh(vertex_bounds.to_pyvista())
plotter.show()
Descend / boundaries update
You can directly update the mesh inplace when computing the descending mesh or the boundaries mesh.
volumes.boundaries_update()
volumes.boundaries_update(target_dim=1)
volumes.to_pyvista(dim="all").shrink(0.8).plot(show_edges=True)
When using the _update version, the elements of the same dimension of the generated mesh are returned as a new mesh.
old_face_mesh = volumes.descend_update()
volumes.to_pyvista(dim="all").shrink(0.8).plot(show_edges=True)
old_face_mesh.to_pyvista().shrink(0.8).plot(show_edges=True)
Connected components
x, y = np.meshgrid(np.linspace(0.0, 1.0, 5), np.linspace(0.0, 1.0, 5))
coords = np.c_[x.flatten(), y.flatten()]
conn = np.array(
[
[0, 1, 6, 5],
[6, 7, 12, 11],
# [2, 3, 8, 7],
[11, 12, 17, 16],
],
dtype=np.uint,
)
mesh = mf.UMesh(coords)
mesh.add_regular_block("QUAD4", conn)
mesh.add_regular_block("VERTEX", np.arange(len(coords), dtype=np.uint)[..., np.newaxis])
compos_link_edge = mesh.connected_components(link_dim=1)
compos_link_node = mesh.connected_components(link_dim=0)
print(f"{len(compos_link_edge)=}")
print(f"{len(compos_link_node)=}")
len(compos_link_edge)=2
len(compos_link_node)=1
edges = mesh.descend()
shape = (3, 2)
row_weights = [1.0, 0.5, 0.5]
groups = [
(0, np.s_[:]),
(1, 0),
(2, 0),
(np.s_[1:], 1),
]
plotter = pv.Plotter(shape=shape, groups=groups, row_weights=row_weights)
plotter.subplot(0, 0)
plotter.add_text("Original mesh")
plotter.add_mesh(mesh.to_pyvista(), show_edges=True)
plotter.camera_position = "xy"
for i, compo in enumerate(compos_link_edge):
plotter.subplot(i + 1, 0)
plotter.add_text(f"Compo linked by edge: n°{i}")
plotter.add_mesh(edges.to_pyvista())
plotter.add_mesh(compo.to_pyvista(), show_edges=True)
plotter.camera_position = "xy"
for i, compo in enumerate(compos_link_node):
plotter.subplot(i + 1, 1)
plotter.add_text(f"Compo linked by node: n°{i}")
plotter.add_mesh(edges.to_pyvista())
plotter.add_mesh(compo.to_pyvista(), show_edges=True)
plotter.camera_position = "xy"
plotter.show()
Crack
This feature is the contrary of the merge_nodes feature. It duplicates nodes such that the resulting mesh does not connect on the descending_mesh given.
x = range(2)
y = np.linspace(0.0, 3.0, 3, endpoint=True)
z = np.logspace(0.0, 1.0, 3, endpoint=True)
volumes = mf.build_cmesh(x, y, z)
faces = volumes.descend()
cracked = volumes.crack(faces)
edges = faces.descend()
compos_original = volumes.connected_components()
compos_cracked = cracked.connected_components()
assert len(compos_original) == 1
n_compos = len(compos_cracked)
shape = (3, n_compos + 1)
groups = [
(0, 0),
(0, np.s_[1:]),
(np.s_[1:], 0),
(1, np.s_[1:]),
*((2, i + 1) for i in range(n_compos)),
]
row_weights = [1.0, 0.1, 1.0]
col_weights = [1.5, *(0.5,) * n_compos]
pv.set_jupyter_backend("static")
plotter = pv.Plotter(
shape=shape, groups=groups, row_weights=row_weights, col_weights=col_weights
)
plotter.subplot(0, 0)
plotter.add_text("Original mesh")
plotter.add_mesh(volumes.to_pyvista(), show_edges=True)
plotter.subplot(0, 1)
plotter.add_text("Cut mesh used for the crack")
plotter.add_mesh(faces.to_pyvista().shrink(0.8), show_edges=True)
plotter.subplot(1, 0)
plotter.add_text("Compo of original mesh")
plotter.add_mesh(edges.to_pyvista())
plotter.add_mesh(compos_original[0].to_pyvista(), show_edges=True)
plotter.subplot(1, 1)
plotter.add_text("Compos of cracked mesh")
for i, compo in enumerate(compos_cracked):
plotter.subplot(2, i + 1)
plotter.add_mesh(edges.to_pyvista())
plotter.add_mesh(compo.to_pyvista(), show_edges=True)
plotter.camera.zoom(2)
plotter.show()
Split
This tool is usefull to split cells into smaller cells. It does not change the topology of the domain not the element type. Using it it gives you 2^n times the number of elements where n is the dimension of of the elements.
x = np.linspace(0.0, 3.0, 2, endpoint=True)
y = np.logspace(0.0, 1.0, 2, endpoint=True)
z = range(2)
mesh = mf.build_cmesh(x, y, z)
mesh_splitted = mesh.split()
pt = pv.Plotter()
pt.add_mesh(
mesh.to_pyvista().shrink(0.95), show_edges=True, edge_color="yellow", line_width=2
)
pt.add_mesh(mesh_splitted.to_pyvista(), style="wireframe", color="red", line_width=2)
pt.show()
Polyze
This functionnality is useful to generate a poly mesh from a regular one. A poly mesh is a mesh of PGON 2d elements and PHED 3d elements.
x = np.linspace(0.0, 3.0, 4, endpoint=True)
y = np.logspace(0.0, 1.0, 4, endpoint=True)
mesh = mf.build_cmesh(x, y)
print(mesh.blocks())
{'QUAD4': array([[ 0, 1, 5, 4],
[ 1, 2, 6, 5],
[ 2, 3, 7, 6],
[ 4, 5, 9, 8],
[ 5, 6, 10, 9],
[ 6, 7, 11, 10],
[ 8, 9, 13, 12],
[ 9, 10, 14, 13],
[10, 11, 15, 14]], dtype=uint64)}
mesh_polyzed = mesh.polyze()
print(mesh_polyzed.blocks())
{'PGON': (array([ 0, 1, 5, 4, 1, 2, 6, 5, 2, 3, 7, 6, 4, 5, 9, 8, 5,
6, 10, 9, 6, 7, 11, 10, 8, 9, 13, 12, 9, 10, 14, 13, 10, 11,
15, 14], dtype=uint64), array([ 4, 8, 12, 16, 20, 24, 28, 32, 36], dtype=uint64))}
mesh_polyzed.to_pyvista().plot(show_edges=True)
unpolyzed = mesh_polyzed.unpolyze()
print(unpolyzed.blocks())
unpolyzed.to_pyvista().plot(show_edges=True)
{'QUAD4': array([[ 0, 1, 5, 4],
[ 1, 2, 6, 5],
[ 2, 3, 7, 6],
[ 4, 5, 9, 8],
[ 5, 6, 10, 9],
[ 6, 7, 11, 10],
[ 8, 9, 13, 12],
[ 9, 10, 14, 13],
[10, 11, 15, 14]], dtype=uint64)}