Search code examples
python3dpyvista

How to smooth pyvista.StructuredGrid?


Here is a Hopf torus made in Python with PyVista:

enter image description here

import numpy as np
import pyvista as pv

A = 0.44
n = 3
def Gamma(t):
    alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
    beta = t + A*np.sin(2*n*t)
    return np.array([
      np.sin(alpha) * np.cos(beta),
      np.sin(alpha) * np.sin(beta),
      np.cos(alpha)
    ])

def HopfInverse(p, phi):
    return np.array([
      (1+p[2])*np.cos(phi),
      p[0]*np.sin(phi) - p[1]*np.cos(phi), 
      p[0]*np.cos(phi) + p[1]*np.sin(phi),
      (1+p[2])*np.sin(phi)
    ]) / np.sqrt(2*(1+p[2]))

def Stereo(q):
    return 2*q[0:3] / (1-q[3])

def F(t, phi):
    return Stereo(HopfInverse(Gamma(t), phi))

angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)

# Display the mesh
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)

The color is not entirely smooth: on the lobe at the bottom right, you can see a line which separates pale gray and dark gray. How to get rid of this line?


Solution

  • I think what's going on here is that there's no connectivity information where the two ends of your structured grid meet. One way to fix this is to turn your grid into a PolyData using the extract_geometry() method, and then using clean with a larger tolerance. This will force pyvista to realise that there's a seam in the mesh where points are doubled, causing the points to be merged and the seam closed:

    import numpy as np
    import pyvista as pv
    
    A = 0.44
    n = 3
    def Gamma(t):
        alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
        beta = t + A*np.sin(2*n*t)
        return np.array([
          np.sin(alpha) * np.cos(beta),
          np.sin(alpha) * np.sin(beta),
          np.cos(alpha)
        ])
    
    def HopfInverse(p, phi):
        return np.array([
          (1+p[2])*np.cos(phi),
          p[0]*np.sin(phi) - p[1]*np.cos(phi), 
          p[0]*np.cos(phi) + p[1]*np.sin(phi),
          (1+p[2])*np.sin(phi)
        ]) / np.sqrt(2*(1+p[2]))
    
    def Stereo(q):
        return 2*q[0:3] / (1-q[3])
    
    def F(t, phi):
        return Stereo(HopfInverse(Gamma(t), phi))
    
    angle = np.linspace(0, 2 * np.pi, 300)
    theta, phi = np.meshgrid(angle, angle)
    x, y, z = F(theta, phi)
    
    # Display the mesh, show seam
    grid = pv.StructuredGrid(x, y, z)
    grid.plot(smooth_shading=True)
    
    # convert to PolyData and clean to remove the seam
    cleaned_poly = grid.extract_geometry().clean(tolerance=1e-6)
    cleaned_poly.plot(smooth_shading=True)
    

    nice Hopf torus figure with no seam

    Your mileage for the tolerance parameter may vary.

    Just as a piece of trivia, we can visualize the original seam by extracting the feature edges of your original grid:

    grid.extract_feature_edges().plot()
    

    Plot showing a circle and a clover-shaped curve at the edges of the toroidal dataset

    These curves correspond to the open edges in your original grid:

    >>> grid.extract_surface().n_open_edges
    1196
    

    Since your surface is closed and watertight, it should have 0 open edges:

    >>> cleaned_poly.n_open_edges
    0