A Python library to create simple 3D Surface Meshs using Quadrilateral dominant faces.
Project description
SurfMesh - A Surface Meshing Python Library
SurfMesh is a Python library for generating structured 3D surface meshes of primitive shapes, with a strong focus on quadrilateral-dominant (quad) meshing. The meshes are particularly suited for visualization and Boundary Element Method (BEM) simulations.
⚠️ This project is currently under active development.
🎯 Objective
This library aims to provide a minimal, intuitive interface for constructing quad-based surface meshes of primitive solids.
Use cases include:
- Geometry visualization
- Boundary Element Methods (BEM)
- Educational tooling
- Preprocessing for surface-based solvers
⚙️ Requirements
- Python: >= 3.10
- Dependencies:
numpy>=1.24- Optional (for examples and visualization):
ipykerneljupyterlabmatplotlib
🚀 Installation
To install stable version via PyPI:
pip install surfmesh
For the latest development version via Git:
pip install git+https://github.com/ckesanapalli/surface-mesher.git
🧱 Basic Usage
Below are examples of how to use the library to generate and visualize meshes.
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import surfmesh as sm
plot_res = (4, 4)
1. Mesh Between Two Edges
# Define two edges
x = np.linspace(0, np.pi / 2, 20)
edge1 = np.array([x, np.sin(x)])
edge2 = np.array([x, np.exp(x)])
# Generate the mesh
radial_resolution = 10
mesh = sm.mesh_between_edges([edge1, edge2], radial_resolution)
print(f"Generated mesh with {mesh.shape[0]} quadrilateral faces.")
fig, ax = plt.subplots(figsize=plot_res)
collection = PatchCollection(map(Polygon, mesh), facecolor='lightblue', edgecolor='k', linewidth=0.3)
ax.add_collection(collection)
ax.set_xlim(mesh[:, :, 0].min() - 0.1, mesh[:, :, 0].max() + 0.1)
ax.set_ylim(mesh[:, :, 1].min() - 0.1, mesh[:, :, 1].max() + 0.1)
ax.set_title("Mesh Between Edges")
plt.show()
Generated mesh with 190 quadrilateral faces.
2. Radial Disk Mesh
# Parameters for the radial disk mesh
radius = 1.0
radial_resolution = 10
segment_resolution = 20
# Generate the radial disk mesh
radial_mesh = sm.disk_mesher_radial(radius, radial_resolution, segment_resolution)
print(f"Generated radial disk mesh with {radial_mesh.shape[0]} quadrilateral faces.")
fig, ax = plt.subplots(figsize=plot_res)
patches = [Polygon(face, closed=True) for face in radial_mesh]
collection = PatchCollection(patches, facecolors="lightblue", edgecolors="k", alpha=0.7)
ax.add_collection(collection)
ax.set_xlim(-radius, radius)
ax.set_ylim(-radius, radius)
ax.set_aspect("equal")
ax.set_title("Radial Disk Mesh")
plt.show()
Generated radial disk mesh with 200 quadrilateral faces.
3. Square-Centered Disk Mesh
radius = 1.0
square_resolution = 5
radial_resolution = 10
square_side_radius_ratio = 0.5
# Generate the square-centered disk mesh
square_centered_mesh = sm.disk_mesher_square_centered(radius, square_resolution, radial_resolution, square_side_radius_ratio)
print(f"Generated square-centered disk mesh with {square_centered_mesh.shape[0]} quadrilateral faces.")
fig, ax = plt.subplots(figsize=plot_res)
collection = PatchCollection(map(Polygon, square_centered_mesh), facecolors="lightgreen", edgecolors="k", alpha=0.7)
ax.add_collection(collection)
ax.set_xlim(-radius, radius)
ax.set_ylim(-radius, radius)
ax.set_aspect("equal")
ax.set_title("Square-Centered Disk Mesh")
plt.show()
Generated square-centered disk mesh with 225 quadrilateral faces.
4. Cuboid Mesh using Explicit Coordinates
# Define coordinate arrays for a cuboid
x_coords = [0.0, 1.0, 2.0]
y_coords = [0.0, 1.0, 2.0]
z_coords = [0.0, 0.5, 1.0]
# Generate the cuboid surface mesh
faces = sm.cuboid_mesher(x_coords, y_coords, z_coords)
print(f"Generated {faces.shape[0]} quadrilateral faces.")
print(faces.shape)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection="3d")
# Add each quad to the 3D plot
poly = Poly3DCollection(faces, facecolors="skyblue", edgecolors="k", alpha=0.7)
ax.add_collection3d(poly)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Cuboid Surface Mesh")
plt.tight_layout()
plt.show()
Generated 24 quadrilateral faces.
(24, 4, 3)
5. Cuboid Mesh using Resolution
# Generate a cuboid mesh with resolution
length, width, height = 2.0, 1.0, 1.0
resolution = (4, 2, 2)
mesh = sm.cuboid_mesher_with_resolution(length, width, height, resolution=resolution)
print(f"Generated cuboid with {mesh.shape[0]} quadrilateral faces.")
print(mesh.shape)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection="3d")
poly = Poly3DCollection(mesh, facecolors="lightgreen", edgecolors="k", alpha=0.6)
ax.add_collection3d(poly)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Cuboid Mesh with Resolution")
plt.tight_layout()
plt.show()
Generated cuboid with 40 quadrilateral faces.
(40, 4, 3)
6. Revolve a Curve Along a Circular Path
# Sample 2D curve coordinates
x = np.linspace(0, 1, 20)
z = x ** 2 # Example curve (parabola)
curves = np.array([x, z]).T
# Revolve the curve
segment_resolution = 20
faces = sm.circular_revolve(curves, segment_resolution, start_angle=0, end_angle=2*np.pi)
# Plotting
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection='3d')
ax.add_collection3d(Poly3DCollection(faces, facecolors='g', linewidths=1, alpha=0.5))
ax.set_xlim(-x.max(), x.max())
ax.set_ylim(-x.max(), x.max())
ax.set_zlim(z.min(), z.max())
ax.set_xlabel('X-axis')
ax.set_ylabel('Y-axis')
ax.set_zlabel('Z-axis')
plt.show()
7. Revolve a Curve Along a Custom Path
x = np.linspace(1, 10, 100)
z = np.log(x)
main_curve = np.array([x, z]).T
angle_rad = np.linspace(0, 4*np.pi, 100)
radius = angle_rad/10
revolve_path = np.array([angle_rad, radius]).T
revolved_mesh = sm.revolve_curve_along_path(main_curve, revolve_path)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection='3d')
ax.add_collection3d(Poly3DCollection(revolved_mesh, alpha=0.5))
ax.set_xlim(-x.max(), x.max())
ax.set_ylim(-x.max(), x.max())
ax.set_zlim(z.min(), z.max())
plt.show()
8. Generate a Radial Cylinder Mesh
radius = 1.0
height = 2.0
radial_resolution = 8
segment_resolution = 16
height_resolution = 10
# Generate the cylinder mesh
mesh = sm.cylinder_mesher_radial(radius, height, radial_resolution, segment_resolution, height_resolution)
print(f"Generated a Radial Cylinder Mesh {mesh.shape[0]}.")
print(mesh.shape)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection="3d")
poly = Poly3DCollection(mesh, facecolors="lightgreen", edgecolors="k", alpha=0.6)
ax.add_collection3d(poly)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Radial Cylinder Mesh")
plt.tight_layout()
plt.show()
Generated a Radial Cylinder Mesh 416.
(416, 4, 3)
9. Generate a Square-Centered Cylinder Mesh
radius = 1.0
height = 2.0
radial_resolution = 8
half_square_side_resolution = 4
height_resolution = 10
# Generate the cylinder mesh
mesh = sm.cylinder_mesher_square_centered(radius, height, radial_resolution, half_square_side_resolution, height_resolution)
print(f"Generated a Square-Centered Cylinder Mesh.")
print(mesh.shape)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection="3d")
poly = Poly3DCollection(mesh, facecolors="lightgreen", edgecolors="k", alpha=0.6)
ax.add_collection3d(poly)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Square-Centered Cylinder Mesh")
plt.tight_layout()
plt.show()
Generated a Square-Centered Cylinder Mesh.
(960, 4, 3)
10. Generate a Sphere Mesh Using Cube Projection
mesh = sm.sphere_mesher_from_projection(radius=1.0, resolution=10)
print(f"Generated a Sphere Mesh from Cube Projection.")
print(mesh.shape)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection="3d")
poly = Poly3DCollection(mesh, facecolors="lightgreen", edgecolors="k", alpha=0.6)
ax.add_collection3d(poly)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Sphere Mesh from Cube Projection")
plt.tight_layout()
plt.show()
Generated a Sphere Mesh from Cube Projection.
(600, 4, 3)
11. Generate a Sphere Mesh Using Radial Divisions
radius = 1.0
radial_resolution = 20
segment_resolution = 20
mesh = sm.sphere_mesher_from_radial(radius, radial_resolution, segment_resolution)
print(f"Generated a Radial Sphere Mesh.")
print(mesh.shape)
fig = plt.figure(figsize=plot_res)
ax = fig.add_subplot(111, projection="3d")
poly = Poly3DCollection(mesh, facecolors="lightgreen", edgecolors="k", alpha=0.6)
ax.add_collection3d(poly)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Sphere Mesh from Radial Sphere")
plt.tight_layout()
plt.show()
Generated a Radial Sphere Mesh.
(400, 4, 3)
12. Extract Faces and Vertices from the mesh
radius = 1.0
radial_resolution = 20
segment_resolution = 20
mesh = sm.sphere_mesher_from_radial(radius, radial_resolution, segment_resolution)
vertices, faces = sm.extract_vertices_faces(mesh.round(6))
print(f"Generated a Radial Sphere Mesh with {faces.shape[0]} faces and {vertices.shape[0]} vertices.")
print(f"Vertices shape: {vertices.shape}, Faces shape: {faces.shape}")
print(f"First 5 vertices:\n{vertices[:5]}")
print(f"First 5 faces:\n{faces[:5]}")
Generated a Radial Sphere Mesh with 400 faces and 382 vertices.
Vertices shape: (382, 3), Faces shape: (400, 4)
First 5 vertices:
[[-1. 0. 0. ]
[-0.987688 0. -0.156434]
[-0.987688 0. 0.156434]
[-0.951057 -0.309017 0. ]
[-0.951057 0. -0.309017]]
First 5 faces:
[[233 235 190 190]
[269 278 235 233]
[295 297 278 269]
[315 328 297 295]
[337 339 328 315]]
📌 Roadmap
- Cuboid surface mesh generation
- Disk face mesh generation
- Revolve curve mesh generation
- Cylinder, and sphere support
- Curvilinear mesh
- STL/PLY export support
- Mesh visualization utilities
- Export to BEM-compatible formats
📄 License
MIT License © 2025 Chaitanya Kesanapalli
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file surfmesh-0.2.10.tar.gz.
File metadata
- Download URL: surfmesh-0.2.10.tar.gz
- Upload date:
- Size: 2.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10320fd62f7e59873aafa99dbe36a5b66a02fde1921586cf9111fc3ec039f1ae
|
|
| MD5 |
01c703a7cc6fb8fa7c62ce13a955594a
|
|
| BLAKE2b-256 |
37411b5d6c522916d99cd00994681be02ba11ff1d2aac5222943165871d8d13c
|
Provenance
The following attestation bundles were made for surfmesh-0.2.10.tar.gz:
Publisher:
python-publish.yml on ckesanapalli/surfmesh
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
surfmesh-0.2.10.tar.gz -
Subject digest:
10320fd62f7e59873aafa99dbe36a5b66a02fde1921586cf9111fc3ec039f1ae - Sigstore transparency entry: 203739040
- Sigstore integration time:
-
Permalink:
ckesanapalli/surfmesh@df1d00fe204084ff6fe929fc4a1dd975351a051d -
Branch / Tag:
refs/tags/v0.2.11 - Owner: https://github.com/ckesanapalli
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@df1d00fe204084ff6fe929fc4a1dd975351a051d -
Trigger Event:
release
-
Statement type:
File details
Details for the file surfmesh-0.2.10-py3-none-any.whl.
File metadata
- Download URL: surfmesh-0.2.10-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58055f4cd16d0bd786442006a319956c9b62313d79a1acdab7b9059cef8176da
|
|
| MD5 |
a36c9c21aec6caa4698561f7743a2ba7
|
|
| BLAKE2b-256 |
b3b5489f0cb58f4e76103c7cb6f6ed3887eab8354f50f8cd1439d03cdde3cc45
|
Provenance
The following attestation bundles were made for surfmesh-0.2.10-py3-none-any.whl:
Publisher:
python-publish.yml on ckesanapalli/surfmesh
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
surfmesh-0.2.10-py3-none-any.whl -
Subject digest:
58055f4cd16d0bd786442006a319956c9b62313d79a1acdab7b9059cef8176da - Sigstore transparency entry: 203739042
- Sigstore integration time:
-
Permalink:
ckesanapalli/surfmesh@df1d00fe204084ff6fe929fc4a1dd975351a051d -
Branch / Tag:
refs/tags/v0.2.11 - Owner: https://github.com/ckesanapalli
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@df1d00fe204084ff6fe929fc4a1dd975351a051d -
Trigger Event:
release
-
Statement type: