Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Geometric transforms

import numpy as np
import pyvista as pv

import mefikit as mf

pv.set_plot_theme("dark")
pv.set_jupyter_backend("static")

coords = np.array(
    [
        [0.0, 0.0],
        [1.0, 0.0],
        [0.0, 1.0],
        [1.0, 1.0],
    ]
)
mesh = mf.UMesh(coords)
mesh.add_regular_block("QUAD4", np.array([[0, 1, 3, 2]], dtype=np.uint))

All angles are given in radians. A Transform is an affine transformation represented by a 4x4 homogeneous matrix (last row 0 0 0 1). UMesh objects are transformed out-of-place: each transform returns a new mesh and leaves the source unchanged.

Out-of-place transforms

translated = mesh.translate([10.0, 0.0])
translated.to_pyvista().plot()

rotated = mesh.rotate([0.0, 0.0, 1.0], np.pi / 6)
rotated.to_pyvista().plot()

mirrored = mesh.rotate([0.0, 0.0, 1.0], np.pi / 6).mirror([1.0, 0.0, 0.0])
mirrored.to_pyvista().plot()

scaled = mesh.scale([2.0, 3.0])
scaled.to_pyvista().plot()

uniform = mesh.scale_uniform(4.0)
uniform.to_pyvista().plot()

The input mesh is never modified.

Unidirectional (left-to-right) composition

Transform supports composition in both conventions:

  • a @ b (matrix product) applies b first;
  • a.then(b) applies a, then b.
tr = mf.Transform.translation([1.0, 0.0]).then(
    mf.Transform.rotation([0.0, 0.0, 1.0], np.pi / 2)
)
out = mesh.transform(tr)
assert np.allclose(out.coords()[1], [0.0, 2.0], atol=1e-9)

matrix = mf.Transform.translation([1.0, 2.0, 3.0]) @ mf.Transform.scaling([2.0])
assert np.allclose(
    matrix.matrix(),
    mf.Transform.translation([1.0, 2.0, 3.0]).matrix()
    @ mf.Transform.scaling([2.0]).matrix(),
)

Rather than a Transform, mesh.transform(...) also accepts a raw 4x4 numpy array, and Transform.from_matrix(...) wraps one.

mat = np.eye(4)
mat[0, 3] = 7.0
assert np.isclose(mesh.transform(mat).coords()[0, 0], 7.0)

Dimensional consistency is enforced: trying to move a 2D (or 1D) mesh out of its plane (or line) raises a ValueError.

try:
    mesh.translate([0.0, 0.0, 0.5])
except ValueError as err:
    print(err)
This transform moves the mesh out of its plane/line: rows beyond the space dimension must leave the extra coordinates unchanged.

Duplicating a mesh

duplicate(step, n) returns n copies, each transformed by the powers step, step @ step, … of step.

ts = mf.Transform.translation([0.0, 3.0, 0.0])
rt = mf.Transform.rotation([0.0, 0.0, 1.0], np.pi / 4)
column = mesh.duplicate(ts @ rt, 3)
assert column.coords().shape[0] == 12
column.to_pyvista().plot(show_edges=True)

Arbitrary arrangements can be built with the module-level aggregate / concat functions, which concatenate meshes while preserving blocks, fields, families and groups (element ids are relabelled so that the resulting mesh stays valid).

line = mf.concat(mesh, mesh.translate([5.0, 0.0]))
three = mf.aggregate([mesh, mesh.translate([5.0, 0.0]), mesh.translate([15.0, 0.0])])
assert line.coords().shape[0] == 8
assert three.coords().shape[0] == 12
line.to_pyvista().plot()
three.to_pyvista().plot()

Transforms preserve the mesh metadata: blocks, fields, families and groups survive untouched.

Reconstructing coordinates

For non-affine coordinate changes, use UMesh.from_mesh. It accepts a same-shaped coordinate array and preserves the selected source connectivity, fields, families and groups. Omitting coords reuses the source coordinates.

coords = mesh.coords()
coords[:, 0] *= 2.0
coords[:, 1] *= coords[:, 0] + 1.0
warped = mf.UMesh.from_mesh(
    mesh,
    coords,
)
# assert np.allclose(warped.coords()[:, 0], mesh.coords()[:, 0] ** 2)
assert np.allclose(warped.coords(), coords)
warped.to_pyvista().plot()

from_mesh can select source element blocks by topological dimension or by element type. The two selectors are mutually exclusive; the source mesh is never changed.

surfaces = mf.UMesh.from_mesh(mesh, dim=2)
quads = mf.UMesh.from_mesh(mesh, element_types=["QUAD4"])

Coordinate arrays are structurally validated when a mesh is reconstructed; geometric quality checks are not performed.