Clipping a mesh, e.g. to a ball
See original GitHub issueHello,
I want to request a feature: the possibility to clip a mesh to a general region. Let me show you what I mean with an example.
Here is the Togliatti (iso)surface clipped to a box:
from math import sqrt
import numpy as np
import pyvista as pv
def f(x, y, z):
return (
64
* (x - 1)
* (
x ** 4
- 4 * x ** 3
- 10 * x ** 2 * y ** 2
- 4 * x ** 2
+ 16 * x
- 20 * x * y ** 2
+ 5 * y ** 4
+ 16
- 20 * y ** 2
)
- 5
* sqrt(5 - sqrt(5))
* (2 * z - sqrt(5 - sqrt(5)))
* (4 * (x ** 2 + y ** 2 - z ** 2) + (1 + 3 * sqrt(5))) ** 2
)
# generate data grid for computing the values
X, Y, Z = np.mgrid[(-5):5:200j, (-5):5:200j, (-4):4:200j]
# create a structured grid
grid = pv.StructuredGrid(X, Y, Z)
values = f(X, Y, Z)
grid.point_data["values"] = values.ravel(order="F")
# compute the isosurface f(x, y, z) = 0
isosurf = grid.contour(isosurfaces=[0])
mesh = isosurf.extract_geometry()
# surface clipped to the box:
mesh.plot(smooth_shading=True, color="hotpink")
It is not highly pretty. You will see later that it is prettier when clipped to a ball (in particular it would be desirable to get rid of the non-connected components at the top).
A way to clip the surface to a ball consists in using remove_points
:
# surface clipped to the ball of radius 4.8, with the help of `remove_points`:
lengths = np.linalg.norm(mesh.points, axis=1)
toremove = lengths >= 4.8
mesh2, idx = mesh.remove_points(toremove)
mesh2.plot(smooth_shading=True, color="hotpink")
That’s not too bad, but that’s not perfect: the borders are not smooth.
In the case of a ball, we can achieve a clipped surface with smooth borders by resorting to spherical coordinates:
from math import sqrt, pi
import numpy as np
import pyvista as pv
def f(ρ, θ, ϕ):
x = ρ * np.cos(θ) * np.sin(ϕ)
y = ρ * np.sin(θ) * np.sin(ϕ)
z = ρ * np.cos(ϕ)
return (
64
* (x - 1)
* (
x ** 4
- 4 * x ** 3
- 10 * x ** 2 * y ** 2
- 4 * x ** 2
+ 16 * x
- 20 * x * y ** 2
+ 5 * y ** 4
+ 16
- 20 * y ** 2
)
- 5
* sqrt(5 - sqrt(5))
* (2 * z - sqrt(5 - sqrt(5)))
* (4 * (x ** 2 + y ** 2 - z ** 2) + (1 + 3 * sqrt(5))) ** 2
)
# generate data grid for computing the values
Rho, Theta, Phi = np.mgrid[0:4.8:200j, 0:pi:200j, 0:(2*pi):200j]
# create a structured grid
grid = pv.StructuredGrid(Rho, Theta, Phi)
values = f(Rho, Theta, Phi)
grid.point_data["values"] = values.ravel(order="F")
# compute the isosurface f(ρ, θ, ϕ) = 0
isosurf = grid.contour(isosurfaces=[0])
mesh = isosurf.extract_geometry()
# spherical to Cartesian:
def sph2cart(sph):
ρ = sph[:, 0]
θ = sph[:, 1]
ϕ = sph[:, 2]
return np.array([
ρ * np.cos(θ) * np.sin(ϕ),
ρ * np.sin(θ) * np.sin(ϕ),
ρ * np.cos(ϕ)
])
mesh.points = np.transpose(sph2cart(mesh.points))
mesh.plot(smooth_shading=True, color="hotpink")
Now the borders are smooth:
Well, the point I want to raise is that there’s no way in PyVista to properly clip a mesh to a given bounding region, or at least I didn’t find one. Is there such a feature available?
Such a feature is available in the R package rgl: clipMesh3d (this package is a wrapper of OpenGL). It would be nice to have this feature in PyVista.
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (5 by maintainers)
Top GitHub Comments
It is fine for this example. I’ve just published a blog post in which I mention this method : MeshClipping.
Nice blog post!