Skip to main content

pySMESH

PyPI

pySMESH

Python bindings to SALOME SMESH and Open CASCADE (OCCT), packaged as one self-contained Windows wheel. No SALOME platform. No CORBA. No GUI.

pySMESH gives a CFD or CAD preprocessing pipeline direct access to a production meshing and geometry kernel, through plain NumPy arrays and BREP bytes. It does not replace SALOME. It exposes the parts of SMESH and OCCT a pipeline needs, as a normal pip-installable module.

Meta:

  • License: LGPL-2.1-only (see LICENSE, NOTICE.md)
  • Platform: Windows x64, CPython 3.11 to 3.14
  • Runtime dependencies: NumPy. Nothing else. SMESH, OCCT, Boost and VTK all ship inside the wheel.

What it covers

pySMESH covers three areas:

  • Session: a stateful CAD modelling session. Build and edit a shape (primitives, booleans, fillets, chamfers, sweeps, healing). Every entity keeps a persistent id across every edit. See CAD modelling.
  • Mesher: SMESH's full meshing pipeline. Assign algorithms and hypotheses per sub-shape, compute a mesh, then edit, query, and check its quality. It also accepts a mesh it did not build: a discrete body with no B-rep goes in as plain arrays. See Mesh generation and editing and Discrete meshes.
  • Standalone OCCT geometry operations: STEP and IGES import/export, tessellation, offsets, distance and leak checks, point-in-solid classification, and geometry queries. See Geometry operations.

Two entry points serve one-shot work outside a session. compute_viscous_layers wraps StdMeshers_ViscousLayers for 3-D boundary-layer prism meshing. unify_same_domain wraps ShapeUpgrade_UnifySameDomain for B-rep face merging that removes STEP import seams.

Install

pip install pysmesh

That is the whole procedure. The wheel is self-contained: SMESH, OCCT, Boost and VTK all ship inside it. NumPy is the only thing pip pulls in.

Platform: Windows x64, CPython 3.11 to 3.14. There is one wheel per interpreter and none for other platforms. Pip picks the right one, and refuses to install on anything unsupported rather than land something that cannot import.

macOS and Linux are not built. The blocker is packaging, not the code: PyPI requires Linux wheels to be manylinux-tagged, and this build resolves OCCT, Boost and VTK from conda-forge, which is a different ABI baseline. Supporting Linux properly means building those dependencies from source inside a manylinux container. That is planned separately, not skipped by oversight.

Wheels are also attached to every GitHub Release, for pinning a build by exact file.

Upgrading from 3.x: 4.0.0 removes the shared VTK requirement. Earlier versions linked the host environment's VTK and refused to import unless it was exactly 9.6.2. That constraint is gone. pySMESH now carries its own private VTK, so it no longer cares which VTK you have, or whether you have one at all. If you install with --no-deps, or gate on _build_info.VTK_VERSION, that check is now obsolete and always passes.

Quick example

import pysmesh
from pysmesh import Session
from pysmesh.session import EntityKind

s = Session()
s.add_box(3.0, 7.0, 11.0)
s.fillet(edge_ids=s.entities(EntityKind.EDGE), radius=0.5)

handoff = s.export_handoff()  # brep bytes + per-entity id arrays, ready for Mesher

mesher = pysmesh.Mesher(pysmesh.load_brep(handoff.brep))
mesher.assign(pysmesh.mesher.Regular1D())
mesher.assign(pysmesh.mesher.NumberOfSegments(count=8))
mesher.assign(pysmesh.mesher.Quadrangle2D())
mesher.assign(pysmesh.mesher.Hexa3D(), on=pysmesh.mesher.SubShape(
    pysmesh.mesher.SubShapeKind.SOLID, 1
))
mesher.compute()
mesh = mesher.mesh()

CAD modelling

Session owns one live shape and gives every entity a persistent id, so edits, undo, and mesh handoff all stay correct as the shape changes.

from pysmesh import Session
from pysmesh.session import EntityKind

s = Session()
s.add_box(3.0, 7.0, 11.0)
s.fillet(edge_ids=s.entities(EntityKind.EDGE), radius=0.5)
mark = s.snapshot()          # O(1)
s.restore(mark)              # O(1)

handoff = s.export_handoff()  # brep bytes + per-entity id arrays, ready for Mesher

Session covers primitives, curve and surface construction, sweeps, booleans with history, fillet and chamfer, transforms, healing, defeaturing, imprinting, tessellation, and geometric queries. See src/pysmesh/session/__init__.py for the full API.

Two queries answer the questions a feature filter asks. surface_parameters reads a face's analytic parameters off its surface: a radius, a cone's taper, a torus's two radii. face_wires splits a face's boundary into its loops, so an inner loop (a hole) is distinguishable from the outer one. A parameter the surface type does not define reads NaN, never a stand-in value.

import numpy as np

faces = s.entities(EntityKind.FACE)
params = s.surface_parameters(faces)

# every cylindrical face under 1 mm across: fillets and small bores
small = params.ids[(np.array(params.types) == "Cylinder") & (params.radius1 < 1.0)]

wires = s.face_wires(faces)
for row in np.flatnonzero(~wires.is_outer):        # one row per hole
    lo, hi = wires.edge_range[row]
    hole_edges = wires.edge_id[lo:hi]

Three queries answer about edges and about the space between entities. curve_geometry is the curve-side surface_parameters: a line's direction, a circle's or an ellipse's centre, plane normal and radii, with NaN on any free-form curve. curve_at gives the point and unit tangent at each parameter of one edge — along increasing parameter, never flipped for a reversed edge, because an edge shared by two faces is FORWARD in one and REVERSED in the other. distance is the exact minimum distance between any two entities, with the witness point on each.

edges = s.entities(EntityKind.EDGE)
curves = s.curve_geometry(edges)

# every circular edge under 1 mm across: the rims of small bores
is_circle = np.array(curves.type) == "Circle"
rims = curves.edge_id[is_circle & (curves.radius[:, 0] < 1.0)]

first, last = s.edge_parameter_bounds([edges[0]])[0]
sample = s.curve_at(edges[0], np.linspace(first, last, 32))
direction = sample.tangents[sample.defined]

# the clearance between two entities, with the witness point on each
gap = s.distance(faces[0], faces[2])   # gap.distance, gap.point_a, gap.point_b

Mesh generation and editing

Mesher builds a volume or surface mesh from a shape, using SMESH's own algorithm and hypothesis model. Assign an algorithm and its hypotheses to a sub-shape. Different sub-shapes can use different algorithms. Compute the mesh, then read it back as NumPy arrays.

from pysmesh import load_brep
from pysmesh.mesher import Hexa3D, Mesher, NumberOfSegments, Quadrangle2D, Regular1D
from pysmesh.mesher import SubShape, SubShapeKind

mesher = Mesher(load_brep(handoff.brep))
mesher.assign(Regular1D())
mesher.assign(NumberOfSegments(count=8))
mesher.assign(Quadrangle2D())
mesher.assign(Hexa3D(), on=SubShape(SubShapeKind.SOLID, 1))
report = mesher.compute()
mesh = mesher.mesh()

Once a mesh exists:

  • Quality controls measure and classify cells: aspect ratio, skew, orientation, and more.
  • Groups name sets of elements. A group survives edits, so a wall named on a coarse mesh is still the wall after conversion to second order.
  • The editor smooths, merges coincident nodes, reorients cells, splits and fuses faces, converts between linear and quadratic, sews free borders, offsets a surface, and deletes elements and nodes.
  • Search locates elements at a point, casts rays through the mesh, finds sharp edges, and classifies a point as inside or outside a closed surface.
  • The medial axis of a face reports its centreline and local wall thickness. A face can also be decomposed into blocks or have a pattern mapped onto it.
  • compute_viscous_layers grows prism boundary layers on a classified surface mesh. See examples/box_bl.py for the end-to-end walkthrough.

See src/pysmesh/mesher/__init__.py for the full model and src/pysmesh/_core.pyi for the typed API.

Discrete meshes (no CAD)

A Mesher does not need a shape. Mesher() starts empty and is filled from arrays, which is how a body that never had a B-rep gets in: an imported STL, OBJ or PLY, a shrink-wrap result, the boundary another mesher produced, or a mesh read back from a file.

from pysmesh import Mesher

mesher = Mesher.from_arrays(points, triangles)   # (N, 3) float64, (M, 3) row indices

# Divide the surface into patches. Without CAD faces, this is what a viewport picks on.
edges = mesher.sharp_edges(angle=40.0)
patches = mesher.separate_faces_by_edges(edges, name_prefix="patch_")

# Delete one patch. The report names every id that went, including the freed nodes.
gone = mesher.remove_elements(patches.at(2), free_nodes=True)
print(gone.elements, gone.nodes)

Three things are worth knowing:

  • Ids are the handle. Nodes and elements keep their ids for as long as they exist, and nothing is ever renumbered. add_nodes and add_elements return the ids they created. Mesher.from_mesh(mesh_data) rebuilds a live mesh from a harvest and keeps every one of them, which is the way back from read_gmf.
  • A patch index is not stable. A patch group is. Each call to separate_faces_by_edges re-derives the partition, so indices can shift once faces have been deleted. Passing name_prefix stores each patch as a group, and SMESH maintains that membership itself: a deleted element leaves the group, survivors keep their place.
  • What such a mesher cannot do. Anything that resolves a sub-shape ordinal: compute, assign/unassign, add_group_on_shape, the pattern mapping, smooth(in_uv_space=True), and the ElementsOnShape and Deflection2D controls. Each refuses by name. Everything else, the editor, search, quality controls, groups by id or by filter, behaves identically. Check with mesher.has_shape.

Geometry operations

Standalone Open CASCADE operations. All are headless (no VTK, no SMESH), take and return BREP bytes and NumPy arrays, and key every result to the same 1-based ordinals Shape.faces() / .edges() / .solids() use.

  • read_step_xde / write_step_xde: STEP import/export through OCCT's XDE stack, preserving product names, per-face colours, and the file's length unit.

  • read_iges / write_iges: IGES import/export, on the same contract.

    Both readers return the geometry in the file's native unit, plus length_unit (metres per unit) and unit_name. Both writers take that unit as a required argument and declare it in the header without rescaling, so a file cannot arrive silently normalised or leave silently mislabelled. The ten accepted names are the keys of IGES_UNITS.

    Neither reader touches OCCT's global Interface_Static unit, and neither does the IGES writer. The STEP writer is the one exception: OCCT exposes no per-writer control over the STEP header's unit, so write_step_xde sets that global for the duration of one export and restores the previous value on every exit path, including an exception. Nothing leaks out of the call.

  • tessellate: render-ready triangulation with per-vertex normals.

  • offset_shape / make_thick_solid: B-rep offset and hollowed thick-solid operations.

  • shape_distance / free_boundary_edges: exact minimum distance between two shapes, and the naked edges that localise a hole in an open shell.

  • point_in_solid: exact inside test against a solid.

  • unify_same_domain: real B-rep face merging that deletes the shared seam from the topology, instead of leaving a mesher hint that still forces nodes along it.

  • Shape: per-entity metadata. Surface type, face adjacency, solids, and centroid-based face matching.

import pysmesh

imp = pysmesh.read_step_xde("blade.step")
shape = pysmesh.load_brep(imp.brep)
shape.faces()[0].surface_type   # "Plane" / "Cylinder" / "Cone" / "Sphere" / "Torus" / ...

mask = pysmesh.point_in_solid(imp.brep, points, tol=1e-7)

# Both formats carry the same unit contract. Coordinates stay native, the unit comes back
# with them, and a re-export declares the unit it was handed.
imp.length_unit                              # 0.001 for an MM file, 1.0 for a metre file
pysmesh.write_step_xde(imp.brep, unit=imp.unit_name)   # round trip, unit-exact

igs = pysmesh.read_iges("housing.igs")
igs.length_unit                              # 0.001 for an MM file, 0.0254 for an INCH file
pysmesh.write_iges(igs.brep, unit=igs.unit_name)

read_iges takes a path, not bytes. OCCT ships no IGES stream reader (IGESSelect_WorkLibrary does not override IFSelect_WorkLibrary::ReadStream). Reading one also makes OCCT print Total number of loaded entities N. to stdout. That is an unconditional info-level message inside IGESFile_Read, and OCCT gives no switch to silence it.

See src/pysmesh/_core.pyi for the full typed API. mypy --strict type-checks against it.

Packaging model

SMESH and OCCT are mature, production-grade CAD and meshing libraries, but neither ships a standalone Python wrapper. Both are normally reached only through the full SALOME platform, wrapped through CORBA/SWIG. That pulls in the entire SALOME GUI and KERNEL stack. pySMESH strips both down to the static library set a CFD preprocessing pipeline needs, and exposes them as a plain, pip-installable module.

Doing that also solves a packaging problem. SMESH pulls in OCCT, Boost and VTK as dependencies. Installing those directly into a host application's environment can trigger a dependency solver cascade that downgrades unrelated packages. pySMESH's build makes that impossible by construction.

  • SMESH and KERNEL are statically linked into a single _core.pyd.
  • OCCT, Boost and VTK are private to that binary. Their DLLs are bundled into the wheel and name-mangled, so they never appear in the host environment and cannot collide with the host's own copies.

Net effect on the host environment: installing the wheel adds one pip entry (pysmesh), plus NumPy. It constrains nothing else. Your application is free to use any VTK it likes, including a different version, because pySMESH never touches it.

That privacy rests on one property, which is worth stating plainly because it is the thing the design protects: no VTK object crosses the Python boundary. Every result leaves as a NumPy array or BREP bytes. A vtkUnstructuredGrid built inside _core would be an instance of a class from the private, name-mangled copy, and therefore a different C++ type from the host's, even at an identical version string. tests/test_vtk_privacy.py fails the build if a binding ever exports one.

Binary size: the 4.0.0 wheel is 41 MB, holding 75 bundled DLLs. OCCT is the largest share at 18.7 MB; private VTK costs 15.7 MB. That is the deliberate trade for zero native footprint in the host environment. _core links only three VTK components (CommonCore, CommonDataModel, FiltersVerdict), so the bundle carries no rendering, IO, or Python-wrapper modules: 17 VTK DLLs out of the 187 MB a full VTK install would put in your environment. CI reports the breakdown on every build and fails if the wheel would exceed PyPI's 100 MB limit.

Build from source

Requires MSVC v143, GNU patch, and a conda-forge build environment. All of VTK, OCCT and Boost are build-time only, and end up inside the wheel:

conda env create -f ci/environment.yml
conda activate <the env name in ci/environment.yml>

python prepare.py                                # stage extern/ -> staged/ and apply patches
pip wheel . --no-build-isolation --no-deps -w dist
# CI additionally repairs the wheel with delvewheel, bundling the whole native closure
# (OCCT + Boost + VTK) and name-mangling every DLL.

For local development (run tests against a freshly built extension without a wheel):

cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_PREFIX_PATH=<env>/Library -DPython_EXECUTABLE=<env>/python.exe
cmake --build build --target _core               # copies _core + _build_info into src/pysmesh
pytest tests/ -q
python examples/box_bl.py

Capability probe

tests/probe is a build-verification target: it constructs and runs every OCCT class and SMESH capability pySMESH depends on, against the exact link set _core uses. Run it after any change to the OCCT toolkit list, the patch series, or the StdMeshers source set. It turns a missing toolkit, a dead-stripped SMESH object, or an un-built translation unit into a named failure instead of a surprise mid-binding.

cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_PREFIX_PATH=<env>/Library -DPython_EXECUTABLE=<env>/python.exe \
      -DPYSMESH_BUILD_V2_PROBE=ON
cmake --build build --target v2_probe
./build/v2_probe.exe                             # exit 0 == every probed capability is usable

Design principles

  • Narrow API. Every exported function exists to serve a concrete CAD or meshing pipeline need. No SWIG, no smeshBuilder emulation, no MED/CGNS I/O. Data crosses the boundary as NumPy arrays and BREP bytes.
  • Fail loud. Every failure is a typed pysmesh.PysmeshError carrying the underlying SMESH/OCCT message and, where applicable, the offending ids. Never a silent best-effort fallback.

Provenance and licensing

Every vendored source and patch is traced in PROVENANCE.md. The third-party component table is in NOTICE.md.

Metadata

Release files for pysmesh 4.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for pysmesh 4.2.2
File
pysmesh-4.2.2-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
pysmesh-4.2.2-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
pysmesh-4.2.2-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
pysmesh-4.2.2-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details

Total release size: 173.4 MB

Release files / pysmesh-4.2.2-cp314-cp314-win_amd64.whl

Download URL pysmesh-4.2.2-cp314-cp314-win_amd64.whl
Size 43.3 MB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
593a6e7a29976c4805a0ac233f5ee12f78f7a5cf98535b530c12378c5e6e7fb0
BLAKE2b-256 checksum
How to use checksums
1939131e91ae884efd374a8ba2c1ea3615c1adc76168a9b074ee42269b5bc720
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / pysmesh-4.2.2-cp313-cp313-win_amd64.whl

Download URL pysmesh-4.2.2-cp313-cp313-win_amd64.whl
Size 43.3 MB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
dec871f2e41096c5d75ade45b642be6dfca6d6f908d6bdb0c0580a29910b2f18
BLAKE2b-256 checksum
How to use checksums
070a379c227e2ea63e57463b8c3557a39e0b4e7e2acae1a6ff5906519522e2a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / pysmesh-4.2.2-cp312-cp312-win_amd64.whl

Download URL pysmesh-4.2.2-cp312-cp312-win_amd64.whl
Size 43.3 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
d028d84095bbf4e8fef34451eb7b2862eb29975c3ca82154fdae2f247b4cce37
BLAKE2b-256 checksum
How to use checksums
61c509de16e569fe4a7775aa91d701192d7d212e51d07c95ffd92e43fd915c4f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / pysmesh-4.2.2-cp311-cp311-win_amd64.whl

Download URL pysmesh-4.2.2-cp311-cp311-win_amd64.whl
Size 43.3 MB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
f9297dd1278c78803b474698e7726b000d753df97ca694902c25dec0cf7e7ba3
BLAKE2b-256 checksum
How to use checksums
c282eac8cb7619acc0313bf19fb51ec467a5c326c64f6c7ab9f2de0899b8140d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

5.1.2

5 release files

5.1.1

4 release files

5.1.0

4 release files

5.0.0

4 release files

This release

4.2.2 This release

4 release files

4.2.1

4 release files

4.2.0

4 release files

4.1.2

4 release files

4.1.1

4 release files

4.1.0

4 release files

4.0.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page