PMEAL / OpenPNM

A Python package for performing pore network modeling of porous media
http://openpnm.org
MIT License
433 stars 172 forks source link

Add full featured `plot_3d` function #2634

Open jgostick opened 1 year ago

jgostick commented 1 year ago

The pyvista package has come VERY far since the last time I checked. It's now very simple to create a 3D interactive plot of a network inside jupyter, with proper glyphs:


import numpy as np
import openpnm as op
import pyvista as pv

pn = op.network.Cubic([5, 5, 5])
pn.add_model_collection(op.models.collections.geometry.spheres_and_cylinders)
pn.regenerate_models()

def plot_3d(network, size_by=None):
    x, y, z = network.coords.T

    # Create and structured surface
    grid = pv.StructuredGrid(x, y, z)
    if size_by is None:
        size_by = network['pore.diameter']
    grid.point_data['size'] = size_by

    # generate glyphs with varying size
    sphere = pv.Sphere()
    spheres = grid.glyph(scale='size', geom=sphere, orient=False)

    spheres.plot(show_scalar_bar=False)
    return spheres

p = plot_3d(pn)  # Call function and catch result
# p.plot()  # Show plot if not already visible
# p.save('test.vtk', binary=False)  # Write to paraview compatible file
jgostick commented 1 year ago

It is also possible to add oriented cylindrical glyphs for the throats, but I have not quite figured that out yet. I also expect it'd be possible to use a wider variety of glyphs beyond what Paraview offers. For instance, boxes with some aspect ratio, or maybe pyramids?

jgostick commented 1 year ago

Someone on the pyvista Discussion has already answered a quesiton which I think pertains to us: https://github.com/pyvista/pyvista/discussions/2635

jgostick commented 1 year ago

This works, but the throat diameter's cannot be scaled independently:


import numpy as np
import openpnm as op
import pyvista as pv

pn = op.network.Cubic([5, 5, 5])
pn.add_model_collection(op.models.collections.geometry.spheres_and_cylinders)
pn.add_model('throat.coords', op.models.geometry.throat_centroid.pore_coords)
pn.add_model('throat.vector', op.models.geometry.throat_vector.pore_to_pore)
pn.regenerate_models()

# %% Create and structured surface
grid = pv.StructuredGrid(*pn.coords.T)
grid.point_data['size'] = pn['pore.diameter']
sphere = pv.Sphere(phi_resolution=180, theta_resolution=180, radius=1)
pores = grid.glyph(scale='size', geom=sphere, orient=False)

# %% Generate throat cylinders
edges_w_padding = np.vstack((np.ones(pn.conns.shape[0], int)*2, pn.conns.T)).T
throats = pv.PolyData(pn.coords, edges_w_padding)
size_by = pn['pore.diameter']

# %% Add both meshes to a plotte object
plotter = pv.Plotter()
plotter.add_mesh(
    pores,
    lighting=True,
    cmap="jet",
    show_scalar_bar=False,
    opacity=0.5)
plotter.add_mesh(
    throats,
    lighting=True,
    scalars=pn['throat.diameter'],
    render_lines_as_tubes=True,
    style='wireframe',
    line_width=10,
    cmap='jet',
    show_scalar_bar=False,
)
plotter.show_axes()
plotter.show()