Make volume meshes the easy way
Project description
PyMeshUp
PyMeshUp provides an easy way to create and modify volume meshes using Python. It combines functionality from pymeshlab, VTK and CadQuery. Optional 3D visualization is available via vedo.
Geomtric data can be read from step files (via cadquery), stl, obj, etc (via pymeshlab) and GHS (custom python code).
The package is primarily aimed at ships and other floating structures.
A gui and compiled version is available separately via pymeshup-gui
Installation
pip install pymeshup
For 3D visualization:
pip install vedo
Quick-start example
from pymeshup import Frame, Hull, Box, Cylinder, Plot
# Define cross-sections
midship = Frame(0, 0, 1, 0, 1, 2, 0, 2).autocomplete() # rectangular half-frame, mirrored
bow = Frame(0, 1) # single point → sharp bow
# Build a hull
hull = Hull(0, midship, 10, midship, 15, bow)
# Combine with a box to keep only the above-waterline part
above_water = hull.crop(zmin=0)
Plot([hull, above_water])
API Reference
Frame
A Frame is a 2-D cross-section (slice). Axes convention: X → right, Y → up.
Construction
from pymeshup import Frame
# From flat coordinate list x1,y1, x2,y2, ...
f = Frame(0, 0, 1, 0, 1, 1, 0, 1)
# From separate x and y lists
f = Frame.from_xy([0, 1, 1, 0], [0, 0, 1, 1])
# Single point (used as a sharp tip)
tip = Frame(0, 1)
The polygon is automatically closed (first point appended to the end if needed).
Methods
| Method | Description |
|---|---|
frame.autocomplete() |
Mirror the half-frame over the x = 0 centre-line and return the full closed frame. |
frame.scaled(x=1, y=1) |
Return a scaled copy. |
frame.copy() |
Return an identical copy. |
frame.center() |
Return the (x, y) centroid. |
frame.is_identical_to(other) |
Return True if both frames share all the same points. |
Properties
| Property | Description |
|---|---|
frame.x |
List of x-coordinates. |
frame.y |
List of y-coordinates. |
frame.xy |
Tuple of (x, y) pairs. |
frame.n |
Number of points (including the closing duplicate). |
Example
from pymeshup import Frame
# Half-frame: bottom-centre → bilge → side → deck
half = Frame(0, 0, 1, 0, 1.5, 0.5, 1.5, 2)
# Mirror to full frame
full = half.autocomplete()
print(full.xy)
# ((0,0),(1,0),(1.5,0.5),(1.5,2),(-1.5,2),(-1.5,0.5),(-1,0),(0,0))
Volume
A Volume is a triangulated 3-D mesh. All operations return a new Volume (non-destructive).
Boolean operations
from pymeshup import Box, Cylinder
box = Box(xmin=-1, xmax=1, ymin=-1, ymax=1, zmin=0, zmax=2)
cyl = Cylinder(height=2, radius=0.4)
combined = box.add(cyl) # union
hollow = box.remove(cyl) # difference – cylinder removed from box
common_part = box.inside_of(cyl) # intersection
Transformations
vol = Box()
moved = vol.move(x=1, y=0, z=0.5) # translate
scaled = vol.scale(x=2, y=1, z=1) # non-uniform scale
rotated = vol.rotate(x=0, y=0, z=45) # Euler angles in degrees
mirror = vol.mirrorXZ() # mirror in the XZ plane (negate Y)
Cropping / cutting
vol = Box(xmin=-2, xmax=2, ymin=-2, ymax=2, zmin=-2, zmax=2)
cropped = vol.crop(zmin=0) # keep z >= 0
submerged = vol.cut_at_waterline() # keep z <= 0
port_side = vol.cut_at_xz() # keep y <= 0
Mesh quality
vol = Hull(0, frame_a, 10, frame_b)
remeshed = vol.regrid(iterations=20, pct=1) # isotropic remeshing
cleaned = vol.merge_close_vertices(pct=1) # weld near-coincident vertices
simple = vol.simplify() # decimate (reduce face count)
Persistence
vol.save("my_mesh.stl") # save to STL (or any format pymeshlab supports)
Properties
| Property | Description |
|---|---|
vol.vertices |
numpy array of vertex positions (N, 3). |
vol.volume |
Signed volume (float). |
vol.center |
Centre of mass [x, y, z]. |
vol.bounds |
(xmin, xmax, ymin, ymax, zmin, zmax). |
Box
Creates a box-shaped volume.
from pymeshup import Box
# Unit box centred at the origin
unit_box = Box()
# Custom box
tank = Box(xmin=0, xmax=5, ymin=-1, ymax=1, zmin=-2, zmax=0)
print(tank.volume) # ≈ 20.0
Signature: Box(xmin=-0.5, xmax=0.5, ymin=-0.5, ymax=0.5, zmin=-0.5, zmax=0.5)
Cylinder
Creates a vertical cylinder with its base at the origin.
from pymeshup import Cylinder
cyl = Cylinder(height=3, radius=0.5, resolution=36)
print(cyl.bounds) # (xmin, xmax, ymin, ymax, 0, 3)
Signature: Cylinder(height=1, radius=1, resolution=36)
The radius is adjusted so that the discretised cylinder has the exact target volume.
Hull
Builds a hull mesh by lofting a sequence of Frame cross-sections along the X axis.
from pymeshup import Frame, Hull
stern = Frame(0, 0, 1, 0, 1, 1).autocomplete()
midship = Frame(0, 0, 2, 0, 2, 2).autocomplete()
bow = Frame(0, 2) # sharp tip
# Hull(x0, frame0, x1, frame1, ..., xn, framen)
vessel = Hull(0, stern, 5, midship, 15, midship, 20, bow)
print(vessel.volume)
Load from CSV file:
vessel = Hull("my_hull_frames.csv")
CSV format (tab- or comma-separated):
x_pos, y1, y2, ...
, z1, z2, ...
x_pos, y1, y2, ...
, z1, z2, ...
Load
Loads an existing mesh file (STL, OBJ, PLY, …).
from pymeshup import Load
mesh = Load("path/to/model.stl")
print(mesh.volume)
Plot
Visualises one or more volumes interactively (requires vedo).
from pymeshup import Box, Cylinder, Plot
a = Box()
b = Cylinder().move(z=1)
Plot([a, b]) # pass a list for multiple volumes
Plot(a) # or a single volume
Install vedo with pip install vedo.
STEP
Loads a STEP file via CadQuery and converts it to a Volume.
from pymeshup import STEP
stp = STEP("model.step", scale=0.001) # scale: e.g. mm → m
vol = stp.to_volume(angular_tolerance=5, linear_tolerance=1)
print(vol.bounds)
STEP.to_volume arguments:
| Argument | Default | Description |
|---|---|---|
angular_tolerance |
5 |
Angular tolerance for tessellation (degrees). |
linear_tolerance |
1 |
Linear tolerance for tessellation. |
filename |
None |
If provided, the intermediate STL is saved to this path. |
GHSgeo
Reads a GHS Geometry File (.GF) and makes individual parts and shapes available as Volume objects.
from pymeshup import GHSgeo
geo = GHSgeo("vessel.GF")
# Access a named part
hull_volume = geo["HULL"] # equivalent to geo.get_volume("HULL")
# Access raw (un-autocompleted) shapes by name
raw_shape = geo.shapes_raw["s1"]
print(raw_shape.bounds)
| Attribute | Description |
|---|---|
geo.parts |
Dictionary of parsed parts, each containing a "volume" key. |
geo.shapes_raw |
Dictionary of shapes as read from the file. |
geo.warnings |
List of warning messages encountered during parsing. |
Polygon Triangulation (helpers)
PyMeshUp ships a polygon-triangulation sub-package used internally by Frame to
cap hull ends and to handle concave cross-sections. You can also call these
utilities directly.
triangulate_poly — automatic backend selection
from pymeshup.helpers.triangulate_non_convex import triangulate_poly
Triangulates any simple (non-self-intersecting) 3-D polygon, including concave shapes. It first tries the fast VTK triangulator; if that fails it falls back to the pure-Python ear-clipping implementation.
from pymeshup.helpers.triangulate_non_convex import triangulate_poly
# A rectangular cross-section at x = -60 (y-z plane)
vertices = [
(-60, 0.0, 0.0),
(-60, -2.5, 0.0),
(-60, -2.5, 4.0),
(-60, 0.0, 4.0),
(-60, 2.5, 4.0),
(-60, 2.5, 0.0),
(-60, 0.0, 0.0), # closing point (same as first)
]
verts, faces = triangulate_poly(vertices)
# verts → list of (x, y, z) tuples (same as input)
# faces → list of (i, j, k) index triples
print(len(faces), "triangles produced") # 4
Returns
| Value | Type | Description |
|---|---|---|
verts |
list[tuple] |
The original vertices (pass-through). |
faces |
list[tuple[int,int,int]] |
Triangle index triples referencing verts. |
triangulate_poly_py — pure-Python ear-clipping
from pymeshup.helpers.earcut_2d import triangulate_poly_py
The pure-Python fallback. Useful when VTK is unavailable or when the polygon is non-planar (the function projects onto the best-fit plane first).
from pymeshup.helpers.earcut_2d import triangulate_poly_py
# A non-planar concave polygon (the "skeg" example from the module docstring)
vertices = [
(-60, 0.0, 0.0),
(-60, -2.5, 0.0),
(-60, -2.5, 4.0),
(-60, 0.0, 4.0),
(-61, 1.0, 5.0), # one vertex is off-plane → triggers projection
(-60, 2.5, 0.0),
(-60, 0.0, 0.0),
]
verts, faces = triangulate_poly_py(vertices)
Low-level earcut helpers
These are used internally by triangulate_poly_py but can be imported
independently if needed.
from pymeshup.helpers.earcut_2d import (
is_clockwise,
is_point_inside_triangle,
triangulate_ear_clipping_2d,
find_plane,
)
| Function | Signature | Description |
|---|---|---|
is_clockwise |
(polygon) |
Returns True when the 2-D vertex list is ordered clockwise. |
is_point_inside_triangle |
(p, a, b, c) |
Returns True when point p lies inside triangle (a, b, c). |
find_plane |
(vertices) |
Given 3-D vertices, returns (ux, uy) — two orthonormal in-plane axes — suitable for projecting the polygon to 2-D. |
triangulate_ear_clipping_2d |
(polygon) |
Ear-clipping triangulation of a 2-D polygon; returns a list of (i, j, k) index triples. |
from pymeshup.helpers.earcut_2d import is_clockwise, triangulate_ear_clipping_2d
square = [(0, 0), (1, 0), (1, 1), (0, 1)]
print(is_clockwise(square)) # False (counter-clockwise)
triangles = triangulate_ear_clipping_2d(square)
print(triangles) # [(0, 1, 2), (2, 3, 0)] or similar
Full worked example
from pymeshup import Frame, Hull, Box, Cylinder, Plot
# --- 1. Build a simple hull ---
half_frame = Frame(0, 0, 3, 0, 3.5, 1, 3.5, 4)
midship = half_frame.autocomplete()
bow = Frame(0, 4) # sharp bow at deck height
hull = Hull(0, midship, 8, midship, 12, bow)
# --- 2. Add a cylindrical funnel on top ---
funnel = Cylinder(height=2, radius=0.4).move(x=4, z=4)
# --- 3. Hollow out an engine-room box ---
engine_room = Box(xmin=1, xmax=5, ymin=-1.5, ymax=1.5, zmin=0, zmax=3)
hull_with_room = hull.remove(engine_room)
# --- 4. Combine ---
ship = hull_with_room.add(funnel)
print(f"Ship volume : {ship.volume:.1f} m³")
print(f"Bounding box: {ship.bounds}")
# Plot(ship) # uncomment if vedo is installed
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 pymeshup-26.3.4.tar.gz.
File metadata
- Download URL: pymeshup-26.3.4.tar.gz
- Upload date:
- Size: 129.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67be226e307a5cffa7c8b37188cfbd9b3fb221f1c8a6e8381da904ea669ebcf3
|
|
| MD5 |
b2bb1488109edde3d1df3f18cd8f7139
|
|
| BLAKE2b-256 |
b309d6cf672bada78b7b8a299593de0539efdcfda8d6fc149916ac3bfe7e0030
|
Provenance
The following attestation bundles were made for pymeshup-26.3.4.tar.gz:
Publisher:
python-publish.yml on RubendeBruin/pymeshup
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymeshup-26.3.4.tar.gz -
Subject digest:
67be226e307a5cffa7c8b37188cfbd9b3fb221f1c8a6e8381da904ea669ebcf3 - Sigstore transparency entry: 1123265910
- Sigstore integration time:
-
Permalink:
RubendeBruin/pymeshup@e0c7b4f7dd9445224e3e2f8b8fbd41f3e9660b24 -
Branch / Tag:
refs/tags/26.03.4 - Owner: https://github.com/RubendeBruin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@e0c7b4f7dd9445224e3e2f8b8fbd41f3e9660b24 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pymeshup-26.3.4-py3-none-any.whl.
File metadata
- Download URL: pymeshup-26.3.4-py3-none-any.whl
- Upload date:
- Size: 38.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46bc6c821577ab2280d6b1f18b72d0406980b8dd177cdaf2481374352c95d96d
|
|
| MD5 |
7650e673eb63349c019e24a571dcd4ec
|
|
| BLAKE2b-256 |
2bdb0397405bdfca051499d7b6ffead426fe745ae5a9371a472237acad8194a3
|
Provenance
The following attestation bundles were made for pymeshup-26.3.4-py3-none-any.whl:
Publisher:
python-publish.yml on RubendeBruin/pymeshup
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymeshup-26.3.4-py3-none-any.whl -
Subject digest:
46bc6c821577ab2280d6b1f18b72d0406980b8dd177cdaf2481374352c95d96d - Sigstore transparency entry: 1123265916
- Sigstore integration time:
-
Permalink:
RubendeBruin/pymeshup@e0c7b4f7dd9445224e3e2f8b8fbd41f3e9660b24 -
Branch / Tag:
refs/tags/26.03.4 - Owner: https://github.com/RubendeBruin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@e0c7b4f7dd9445224e3e2f8b8fbd41f3e9660b24 -
Trigger Event:
release
-
Statement type: