Selection tool
import mefikit as mf
import numpy as np
import pyvista as pv
pv.set_plot_theme("dark")
pv.set_jupyter_backend("static")
Element selection
Elements can be selected based on there:
- types
- ids
- dimensions
Elements can be selected based on there centroid position. The selection methods using centroids are :
- bbox
- sphere
- rectangle
- circle
As you can guess, bbox and sphere should be used for 3d meshes and rectangle and circle for 2d meshes.
Elements can be selected based on the nodes position and a boolean : whether to select element with all nodes matching the coundition or any node matching the coundition.
- nbbox
- nsphere
- nrect
- ncircle
- nids
Elements can be selected based on their group appartenance :
- inside
- outside
Elements can be selected based on their scalar fields values :
- FieldExpr > FieldExpr
- FieldExpr >= FieldExpr
- FieldExpr < FieldExpr
- FieldExpr <= FieldExpr
- FieldExpr == FieldExpr
The Selection type
Here the types manipulated are Selection. They are simple objects that know how to compose themselves.
clip = mf.sel.bbox([-np.inf, -np.inf, -np.inf], [np.inf, np.inf, 0.5])
sphere = mf.sel.sphere([0.5, 0.5, 0.5], 0.5)
x = np.linspace(0.0, 1.0, 20, endpoint=True)
volumes = mf.build_cmesh(x, x, x)
volumes.select(clip).to_pyvista().plot()

volumes.select(sphere).to_pyvista().plot()

Selection composition
One of the great strength of the select method is its composability ! Whatch by yourself.
The operators &, |, ^, - and ~ are available.
x = np.linspace(0.0, 2.0, 100)
y = np.linspace(0.0, 1.0, 50)
faces = mf.build_cmesh(x, y)
circle1 = mf.sel.circle([0.75, 0.5], 0.5)
circle2 = mf.sel.circle([1.25, 0.5], 0.5)
union = faces.select(circle1 | circle2)
pt = pv.Plotter()
pt.add_mesh(faces.descend().to_pyvista())
pt.add_mesh(union.to_pyvista())
pt.camera_position = "xy"
pt.show()

intersection = faces.select(circle1 & circle2)
pt = pv.Plotter()
pt.add_mesh(faces.descend().to_pyvista())
pt.add_mesh(intersection.to_pyvista())
pt.camera_position = "xy"
pt.show()

sym_diff = faces.select(circle1 ^ circle2)
pt = pv.Plotter()
pt.add_mesh(faces.descend().to_pyvista())
pt.add_mesh(sym_diff.to_pyvista())
pt.camera_position = "xy"
pt.show()

diff = faces.select(circle1 - circle2)
pt = pv.Plotter()
pt.add_mesh(faces.descend().to_pyvista())
pt.add_mesh(diff.to_pyvista())
pt.camera_position = "xy"
pt.show()

notsel = faces.select(~circle1)
pt = pv.Plotter()
pt.add_mesh(faces.descend().to_pyvista())
pt.add_mesh(notsel.to_pyvista())
pt.camera_position = "xy"
pt.show()

A 3D complex example
sphere = mf.sel.sphere([0.5, 0.5, 0.5], 0.5)
clip_x = mf.sel.bbox([0.5, -np.inf, -np.inf], [np.inf, np.inf, np.inf]) # x > 0.5
clip_z = mf.sel.bbox([-np.inf, -np.inf, -np.inf], [np.inf, np.inf, 0.5]) # z < 0.5
volumes.select(
(clip_x & sphere & clip_z) | (sphere & ~clip_x & ~clip_z)
).to_pyvista().plot()

How does it works ?
Each objects generated by a selection function (a function from the mf.sel module) is of the Selection type. It implements operators so that it knows how to compose in an expression. That way an expression generates a new Selection which can be interpreted by the .select(expr) method.
print(sphere)
CentroidSelection(
Sphere {
center: [
0.5,
0.5,
0.5,
],
r2: 0.5,
},
)
print(~(sphere & clip_x))
NotExpr(
NotExpr(
BinarayExpr(
BinarayExpr {
operator: And,
left: CentroidSelection(
Sphere {
center: [
0.5,
0.5,
0.5,
],
r2: 0.5,
},
),
right: CentroidSelection(
BBox {
min: [
0.5,
-inf,
-inf,
],
max: [
inf,
inf,
inf,
],
},
),
},
),
),
)
You can see two layers of NotExpr and BinaryExpr. That is perfectly normal, it does not mean that the operation is applied twice, both NotExpr operations and both BinaryExpr op actually comes from different namespaces and it is just a form of encapsulation (first is a variant, second is an enum).