Skip to main content

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 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymeshup-26.3.3.tar.gz (125.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymeshup-26.3.3-py3-none-any.whl (38.1 kB view details)

Uploaded Python 3

File details

Details for the file pymeshup-26.3.3.tar.gz.

File metadata

  • Download URL: pymeshup-26.3.3.tar.gz
  • Upload date:
  • Size: 125.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pymeshup-26.3.3.tar.gz
Algorithm Hash digest
SHA256 f98d5e71e0c55422da9cab0fd3430c525aadb46b0358d6fe9800313c6fedf7c0
MD5 237cfe7a97705d299855e17ea838b2bd
BLAKE2b-256 6ff80f2f2af0b65481f27a54f1d901a90d13cd9965d35552d95837ea959d32b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymeshup-26.3.3.tar.gz:

Publisher: python-publish.yml on RubendeBruin/pymeshup

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymeshup-26.3.3-py3-none-any.whl.

File metadata

  • Download URL: pymeshup-26.3.3-py3-none-any.whl
  • Upload date:
  • Size: 38.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pymeshup-26.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 bafc1decbe3f3eccbf7accde79e6b228ab44cc98312b1bae6d35da991517e3b7
MD5 3479af98b9ca34a31c188e80411e94fc
BLAKE2b-256 ddc4764f81ef62e64489294f7862a5d18fed399ea2f1439d0bf0808967a8aae8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymeshup-26.3.3-py3-none-any.whl:

Publisher: python-publish.yml on RubendeBruin/pymeshup

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page