Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/img/mesh/airplane.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions docs/img/mesh/airplane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Render the PyVista stock airplane mesh as a plain wireframe surface."""

from pathlib import Path

import pyvista as pv

from physicsnemo.mesh.io import from_pyvista, to_pyvista

pv.OFF_SCREEN = True

OUTPUT = Path(__file__).parent / "airplane.png"

### Load the airplane from PyVista examples and convert
pv_airplane = pv.examples.load_airplane()
mesh = from_pyvista(pv_airplane)

pv_mesh = to_pyvista(mesh)

plotter = pv.Plotter(window_size=(1400, 1000))
plotter.add_mesh(
pv_mesh,
color="lightblue",
show_edges=True,
line_width=0.5,
)
plotter.set_background("white")
### Scale-relative isometric-style camera. The airplane lives in a ~2000-unit
### bounding box, so the camera is offset by a fraction of the bounding diagonal.
center = mesh.points.mean(dim=0).numpy().tolist()
diag = float((mesh.points.amax(dim=0) - mesh.points.amin(dim=0)).norm())
eye = [center[0] - 0.7 * diag, center[1] - 0.7 * diag, center[2] + 0.6 * diag]
plotter.camera_position = [eye, center, (0, 0, 1)]
plotter.screenshot(OUTPUT, transparent_background=False)
plotter.close()

print(f"Saved {OUTPUT}")
Binary file added docs/img/mesh/airplane_gaussian_curvature.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions docs/img/mesh/airplane_gaussian_curvature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Render the PyVista stock airplane mesh colored by Gaussian curvature."""

from pathlib import Path

import numpy as np
import pyvista as pv
import torch

from physicsnemo.mesh.io import from_pyvista, to_pyvista

pv.OFF_SCREEN = True

OUTPUT = Path(__file__).parent / "airplane_gaussian_curvature.png"

### Load the airplane from PyVista examples and convert
pv_airplane = pv.examples.load_airplane()
mesh = from_pyvista(pv_airplane)

### Defensive cleanup; harmless on the airplane mesh and matches the bunny
### pipeline so users get a consistent recipe across the docs.
mesh = mesh.clean()

### Subdivide twice for smoother curvature estimation; Loop subdivision
### produces a limit surface that is C2 everywhere except at extraordinary
### vertices, so two levels dramatically reduce discrete-curvature noise.
mesh = mesh.subdivide(levels=2, filter="loop")

### Compute Gaussian curvature with log1p regularization for visualization
K = mesh.gaussian_curvature_vertices
K = torch.nan_to_num(K, nan=0.0)
K_reg = K.sign() * K.abs().log1p()

### Smooth the scalar field via iterated Laplacian diffusion to suppress
### per-vertex noise from the discrete curvature estimate.
adj = mesh.get_point_to_points_adjacency()
src, tgt = adj.expand_to_pairs()
for _ in range(50):
neighbor_sum = torch.zeros_like(K_reg)
counts = torch.zeros_like(K_reg)
neighbor_sum.scatter_add_(0, tgt, K_reg[src])
counts.scatter_add_(0, tgt, torch.ones_like(K_reg[src]))
K_reg = 0.3 * K_reg + 0.7 * neighbor_sum / counts.clamp(min=1)

mesh.point_data["gaussian_curvature"] = K_reg

K_np = K_reg.numpy()
### The airplane is dominated by flat regions punctuated by very high
### curvature at sharp edges, so a tighter upper percentile (80 vs 95)
### lets the bulk variation occupy more of the colormap rather than being
### compressed to a single colour by extreme outliers at the wing tips.
low, high = np.percentile(K_np, 5), np.percentile(K_np, 80)

pv_mesh = to_pyvista(mesh)

plotter = pv.Plotter(window_size=(1400, 1000))
plotter.add_mesh(
pv_mesh,
scalars="gaussian_curvature",
cmap="coolwarm",
clim=(low, high),
show_edges=False,
scalar_bar_args={"title": "Gaussian Curvature", "color": "black"},
)
plotter.set_background("white")
### Scale-relative isometric-style camera, shared with the other airplane scripts.
center = mesh.points.mean(dim=0).numpy().tolist()
diag = float((mesh.points.amax(dim=0) - mesh.points.amin(dim=0)).norm())
eye = [center[0] - 0.7 * diag, center[1] - 0.7 * diag, center[2] + 0.6 * diag]
plotter.camera_position = [eye, center, (0, 0, 1)]
plotter.screenshot(OUTPUT, transparent_background=False)
plotter.close()

print(f"Saved {OUTPUT}")
Binary file added docs/img/mesh/airplane_mean_curvature.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/img/mesh/airplane_mean_curvature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Render the PyVista stock airplane mesh colored by mean curvature."""

from pathlib import Path

import numpy as np
import pyvista as pv
import torch

from physicsnemo.mesh.io import from_pyvista, to_pyvista

pv.OFF_SCREEN = True

OUTPUT = Path(__file__).parent / "airplane_mean_curvature.png"

### Load the airplane from PyVista examples and convert
pv_airplane = pv.examples.load_airplane()
mesh = from_pyvista(pv_airplane)

### Defensive cleanup; harmless on the airplane mesh and matches the bunny
### pipeline so users get a consistent recipe across the docs.
mesh = mesh.clean()

### Subdivide twice for smoother curvature estimation; Loop subdivision
### produces a limit surface that is C2 everywhere except at extraordinary
### vertices, so two levels dramatically reduce discrete-curvature noise.
mesh = mesh.subdivide(levels=2, filter="loop")

### Compute mean curvature with log1p regularization for visualization.
### The airplane has open boundary edges, so ~4% of vertices have undefined
### mean curvature (NaN) which we replace with zero.
H = mesh.mean_curvature_vertices
H = torch.nan_to_num(H, nan=0.0)
H_reg = H.sign() * H.abs().log1p()

### Smooth the scalar field via iterated Laplacian diffusion to suppress
### per-vertex noise from the discrete curvature estimate.
adj = mesh.get_point_to_points_adjacency()
src, tgt = adj.expand_to_pairs()
for _ in range(50):
neighbor_sum = torch.zeros_like(H_reg)
counts = torch.zeros_like(H_reg)
neighbor_sum.scatter_add_(0, tgt, H_reg[src])
counts.scatter_add_(0, tgt, torch.ones_like(H_reg[src]))
H_reg = 0.3 * H_reg + 0.7 * neighbor_sum / counts.clamp(min=1)

mesh.point_data["mean_curvature"] = H_reg

H_np = H_reg.numpy()
### The airplane is dominated by flat regions punctuated by very high
### curvature at sharp edges, so a tighter upper percentile (80 vs 95)
### lets the bulk variation occupy more of the colormap rather than being
### compressed to a single colour by extreme outliers at the wing tips.
low, high = np.percentile(H_np, 5), np.percentile(H_np, 80)

pv_mesh = to_pyvista(mesh)

plotter = pv.Plotter(window_size=(1400, 1000))
plotter.add_mesh(
pv_mesh,
scalars="mean_curvature",
cmap="coolwarm",
clim=(low, high),
show_edges=False,
scalar_bar_args={"title": "Mean Curvature", "color": "black"},
)
plotter.set_background("white")
### Scale-relative isometric-style camera, shared with the other airplane scripts.
center = mesh.points.mean(dim=0).numpy().tolist()
diag = float((mesh.points.amax(dim=0) - mesh.points.amin(dim=0)).norm())
eye = [center[0] - 0.7 * diag, center[1] - 0.7 * diag, center[2] + 0.6 * diag]
plotter.camera_position = [eye, center, (0, 0, 1)]
plotter.screenshot(OUTPUT, transparent_background=False)
plotter.close()

print(f"Saved {OUTPUT}")
42 changes: 21 additions & 21 deletions physicsnemo/mesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ graphics/CAD mesh.

Then, with `mesh.draw()`, you can visualize the mesh:

![Airplane Mesh](examples/readme_examples/airplane.png)
![Airplane Mesh](../../docs/img/mesh/airplane.png)

### Computing Curvature

Expand All @@ -210,7 +210,7 @@ mesh.draw(
)
```

![Gaussian Curvature](examples/readme_examples/airplane_gaussian_curvature.png)
![Gaussian Curvature](../../docs/img/mesh/airplane_gaussian_curvature.png)

*Warmer colors indicate positive Gaussian curvature (convex regions), cooler colors
indicate negative Gaussian curvature (concave regions).*
Expand All @@ -226,7 +226,7 @@ mesh.draw(
)
```

![Mean Curvature](examples/readme_examples/airplane_mean_curvature.png)
![Mean Curvature](../../docs/img/mesh/airplane_mean_curvature.png)

*Warmer colors indicate positive mean curvature (convex regions), cooler colors
indicate negative mean curvature (concave regions).*
Expand Down Expand Up @@ -422,7 +422,7 @@ neighbors of mesh elements (i.e., based on the mesh connectivity,as opposed to
Note that these use an efficient sparse (`indices`, `offsets`) encoding of the
adjacency relationships, which is used internally for all computations. (See the
dedicated
[`physicsnemo.mesh.neighbors._adjacency.py`](physicsnemo/mesh/neighbors/_adjacency.py)
[`physicsnemo.mesh.neighbors._adjacency.py`](neighbors/_adjacency.py)
module.) You can convert these to a typical ragged list-of-lists representation
with `.to_list()`, which is useful for debugging or interoperability, at the
cost of performance:
Expand Down Expand Up @@ -540,35 +540,35 @@ Key design decisions enable these principles:

## Documentation & Resources

- **Examples**: See [`examples/`](examples/) directory for runnable demonstrations
- **Tests**: See [`test/`](test/) directory for comprehensive test suite showing usage
patterns
- **Source**: Explore [`physicsnemo/mesh/`](physicsnemo/mesh/) for implementation details
- **Examples**: See [`examples/`](../../examples/) directory for runnable demonstrations
- **Tests**: See [`test/mesh/`](../../test/mesh/) directory for comprehensive test
suite showing usage patterns
- **Source**: Explore the source modules in this directory for implementation details

**Module Organization:**

- [`physicsnemo.mesh.calculus`](physicsnemo/mesh/calculus/) - Discrete differential
- [`physicsnemo.mesh.calculus`](calculus/) - Discrete differential
operators
- [`physicsnemo.mesh.curvature`](physicsnemo/mesh/curvature/) - Gaussian and mean
- [`physicsnemo.mesh.curvature`](curvature/) - Gaussian and mean
curvature
- [`physicsnemo.mesh.subdivision`](physicsnemo/mesh/subdivision/) - Mesh refinement
- [`physicsnemo.mesh.subdivision`](subdivision/) - Mesh refinement
schemes
- [`physicsnemo.mesh.boundaries`](physicsnemo/mesh/boundaries/) - Boundary detection
- [`physicsnemo.mesh.boundaries`](boundaries/) - Boundary detection
and facet extraction
- [`physicsnemo.mesh.neighbors`](physicsnemo/mesh/neighbors/) - Adjacency computations
- [`physicsnemo.mesh.spatial`](physicsnemo/mesh/spatial/) - BVH and spatial queries
- [`physicsnemo.mesh.sampling`](physicsnemo/mesh/sampling/) - Point sampling and
- [`physicsnemo.mesh.neighbors`](neighbors/) - Adjacency computations
- [`physicsnemo.mesh.spatial`](spatial/) - BVH and spatial queries
- [`physicsnemo.mesh.sampling`](sampling/) - Point sampling and
interpolation
- [`physicsnemo.mesh.transformations`](physicsnemo/mesh/transformations/) - Geometric
- [`physicsnemo.mesh.transformations`](transformations/) - Geometric
operations
- [`physicsnemo.mesh.repair`](physicsnemo/mesh/repair/) - Mesh cleaning and topology
- [`physicsnemo.mesh.repair`](repair/) - Mesh cleaning and topology
repair
- [`physicsnemo.mesh.validation`](physicsnemo/mesh/validation/) - Quality metrics
- [`physicsnemo.mesh.validation`](validation/) - Quality metrics
and statistics
- [`physicsnemo.mesh.visualization`](physicsnemo/mesh/visualization/) - Matplotlib
- [`physicsnemo.mesh.visualization`](visualization/) - Matplotlib
and PyVista backends
- [`physicsnemo.mesh.io`](physicsnemo/mesh/io/) - PyVista import/export
- [`physicsnemo.mesh.examples`](physicsnemo/mesh/examples/) - Example mesh generators
- [`physicsnemo.mesh.io`](io/) - PyVista import/export
- [`physicsnemo.mesh.primitives`](primitives/) - Example mesh generators

---

Expand Down
Loading