Built on Gmsh
apeGmsh is a wrapper built on top of the (awesome) Gmsh Python API. It adds a set of abstractions over the main API to fit an intended structural-FEM workflow — parts, constraints, loads, masses, and an OpenSees bridge. You still have the full Gmsh API underneath whenever you need it.
Where do you want to start?¶
-
I'm new — orient me.
A conversational walkthrough of the session model, naming (tags / labels / physical groups), queries, and CAD import. The right place to start.
-
Show me a working model.
Hello-plate, cantilever, portal frame, modal, pushover — a curated gallery of notebooks rendered inline.
-
I'm meshing, constraining, and loading.
The composite-by-composite reference: parts, mesh sizing, sections, constraints, loads, masses.
-
I'm post-processing.
Five strategies for obtaining results, the slab-based read API, and the post-solve viewer.
-
Why is it built this way?
Principles, the broker freeze, parts & assembly, the spec-as-seam pattern for the OpenSees bridge.
-
Look up a method.
Complete API surface — session composites, mesh, OpenSees bridge, parts, constraints, loads, masses, results, viewers.
What's new¶
-
Five-strategy results pipeline
A single declarative spec drives five execution paths — script export, live recorders, domain capture, MPCO export, live MPCO.
-
ResultsViewer redesign (B0–B5)
Post-solve viewer: diagrams (contour, vector glyph, line force, fiber section, layer stack, gauss markers, spring force, applied loads, reactions), scrubber, persistent sessions, multi-stage navigation.
-
Native + MPCO + transcoder readers
One slab-based composite API across three on-disk formats.
pg=/label=/selection=selection vocabulary all the way. -
v1.4 – v1.5 polish
Import banner with
APEGMSH_QUIET=1opt-out, event-loop dispatcher for the viewers, per-card Apply on diagram layers, per-Geometry display fix, and applied-loads & reactions diagrams.
apeGmsh¶
Structural-FEM wrapper around Gmsh with a composition-based API and a
snapshot FEM broker. Designed to make it cheap to describe a model
once (geometry + physical groups + loads + constraints) and feed it
to any solver. OpenSees has first-class support; other solvers can be
plugged in through the same FEMData contract.
New to the library? Start with the First steps guide — a conversational walk through the session model, naming system (tags / labels / physical groups), queries and selection, booleans, and CAD import.
Documentation: https://nmorabowen.github.io/apeGmsh/
[!NOTE] Built on Gmsh. apeGmsh is a wrapper built on top of the (awesome) Gmsh Python API. It adds a set of abstractions over the main API to fit an intended structural-FEM workflow — parts, constraints, loads, masses, and an OpenSees bridge. You still have the full Gmsh API underneath whenever you need it.
Installation¶
Not on PyPI yet — install directly from the repo:
# Latest from main
pip install "git+https://github.com/nmorabowen/apeGmsh.git@main"
# With all optional dependencies
pip install "apeGmsh[all] @ git+https://github.com/nmorabowen/apeGmsh.git@main"
Or clone for editable development:
Requires Gmsh (with Python bindings), NumPy, and Pandas. Optional
extras: matplotlib (plotting), openseespy (analysis),
pyvista + PyQt6 (Qt viewers).
Quick start¶
from apeGmsh import apeGmsh
with apeGmsh(model_name="plate") as g:
# 1. Geometry — sub-composites under g.model
p1 = g.model.geometry.add_point(0, 0, 0, lc=10)
p2 = g.model.geometry.add_point(100, 0, 0, lc=10)
p3 = g.model.geometry.add_point(100, 50, 0, lc=10)
p4 = g.model.geometry.add_point(0, 50, 0, lc=10)
l1 = g.model.geometry.add_line(p1, p2)
l2 = g.model.geometry.add_line(p2, p3)
l3 = g.model.geometry.add_line(p3, p4)
l4 = g.model.geometry.add_line(p4, p1)
loop = g.model.geometry.add_curve_loop([l1, l2, l3, l4])
surf = g.model.geometry.add_plane_surface(loop)
# 2. Mesh — sub-composites under g.mesh
g.mesh.sizing.set_global_size(10)
g.mesh.generation.generate(dim=2)
g.mesh.partitioning.renumber(dim=2, method="simple", base=1)
# 3. Snapshot for the solver
fem = g.mesh.queries.get_fem_data(dim=2)
print(fem.info)
On import, apeGmsh prints an ASCII banner with the version to
stderr. Set APEGMSH_QUIET=1 to suppress it (useful for tests
and CI).
Architecture¶
apeGmsh is a session object that owns a single gmsh kernel and a
set of focused composites. There is no Assembly class — the
session is the assembly. Parts are registered into
g.parts, meshed together, and queried by label.
Session composites¶
| Access | Purpose |
|---|---|
g.model |
OCC geometry (see sub-composites below) |
g.parts |
Part instances & assembly-level bookkeeping |
g.physical |
Named physical groups (pre-mesh, entity-driven) |
g.mesh |
Meshing (see sub-composites below) |
g.mesh_selection |
Post-mesh node/element selection sets |
g.constraints |
Solver-agnostic constraint definitions & resolver |
g.loads |
Load patterns & definitions (resolved into fem.nodes.loads / fem.elements.loads) |
g.masses |
Mass definitions (resolved into fem.nodes.masses) |
| (post-session) | apeSees(fem) — OpenSees bridge (separate post-session object; see below) |
g.inspect |
Session-level diagnostics |
g.plot |
Matplotlib visualisations (optional) |
g.view |
Gmsh post-processing scalar/vector views |
g.model sub-composites¶
Five focused namespaces for OCC geometry:
| Access | Methods |
|---|---|
g.model.geometry |
add_point, add_line, add_box, add_sphere, add_cylinder, ... |
g.model.boolean |
fuse, cut, intersect, fragment |
g.model.transforms |
translate, rotate, scale, mirror, copy, extrude, revolve, sweep, thru_sections |
g.model.io |
load_step, save_step, load_iges, save_iges, load_dxf, save_dxf, load_msh, save_msh, heal_shapes |
g.model.queries |
bounding_box, center_of_mass, mass, boundary, boundary_curves, boundary_points, adjacencies, entities_in_bounding_box, registry, remove, remove_duplicates, make_conformal, plane, line, select |
Plus g.model.selection (entity selection) and flat g.model.sync(),
g.model.viewer().
g.mesh sub-composites¶
Seven focused namespaces for meshing:
| Access | Methods |
|---|---|
g.mesh.generation |
generate, set_order, refine, optimize, set_algorithm, set_algorithm_by_physical |
g.mesh.sizing |
set_global_size, set_size_global, set_size, set_size_all_points, set_size_sources, set_size_callback, set_size_by_physical |
g.mesh.field |
distance, threshold, box, math_eval, boundary_layer, minimum, set_background |
g.mesh.structured |
set_transfinite_{curve,surface,volume,automatic}, set_recombine, recombine, set_smoothing, set_compound |
g.mesh.editing |
embed, set_periodic, clear, reverse, relocate_nodes, remove_duplicate_{nodes,elements}, affine_transform, crack, import_stl, classify_surfaces, create_geometry |
g.mesh.queries |
get_nodes, get_elements, get_fem_data, get_element_properties, get_element_qualities, quality_report |
g.mesh.partitioning |
renumber, partition, partition_explicit, unpartition, n_partitions, summary, entity_table, save |
Plus flat g.mesh.viewer() and g.mesh.results_viewer() for
interactive windows.
apeSees(fem) — post-session OpenSees bridge¶
The OpenSees bridge is constructed after the session closes, from a
FEMData snapshot. It is not a g. composite — the in-session
g.opensees attribute was removed in Phase 8 (ADR 0009).
from apeGmsh.opensees import apeSees
fem = g.mesh.queries.get_fem_data(dim=3)
ops = apeSees(fem)
ops.model(ndm=3, ndf=3)
apeSees has no ingest and no auto-resolution. Loads, masses, and
SPs must be re-declared explicitly on ops. Multi-point constraint
emission is deferred (ADR 0009).
| Namespace | What it provides |
|---|---|
ops.nDMaterial.* |
nD material typed constructors; return handles |
ops.uniaxialMaterial.* |
uniaxial material typed constructors; return handles |
ops.section.* |
section typed constructors; return handles |
ops.geomTransf.* |
Linear / PDelta / Corotational; return handles |
ops.beamIntegration.* |
Lobatto, Legendre, …; return handles |
ops.element.* |
element typed constructors; pg= resolved FEM-direct |
ops.fix(pg=, dofs=) |
homogeneous SP (model-level) |
ops.mass(pg=, values=) |
lumped nodal mass |
ops.timeSeries.* |
Linear, Constant, Path, Trig, Pulse |
ops.pattern.* |
Plain, UniformExcitation (context-managers) |
ops.recorder.* |
typed recorder declarations |
ops.tcl(path) |
emit Tcl deck |
ops.py(path, run=False) |
emit Python deck |
ops.h5(path) |
emit native HDF5 |
ops.run() |
in-process openseespy |
ops.analyze(steps, dt) |
drive the analysis chain |
The FEM broker¶
get_fem_data(dim) returns a FEMData snapshot — a solver-agnostic
container organised into .nodes and .elements composites plus
.info, .inspect, and .mesh_selection. It's the single contract
between Gmsh and any downstream solver.
g.mesh.partitioning.renumber(dim=3, method="rcm", base=1)
fem = g.mesh.queries.get_fem_data(dim=3)
# fem.nodes.ids / fem.nodes.coords — node IDs, coordinates
# fem.elements.ids / fem.elements.connectivity — element IDs, connectivity
# fem.nodes.get(pg=...) / .get(label=...) — subset by group / label
# fem.info — mesh stats (n_nodes, n_elems, bandwidth)
# fem.mesh_selection — post-mesh selection sets
# fem.nodes.loads / fem.elements.loads — resolved NodalLoadRecord / ElementLoadRecord
# fem.nodes.masses — resolved MassRecord
# fem.nodes.constraints / fem.elements.constraints — resolved constraint records
# fem.inspect.summary() — human-readable broker summary
Constraints, loads, masses¶
Two-stage pipeline:
- Define (pre-mesh):
g.constraints.equal_dof(...),g.loads.point("TopFace", force_xyz=...),g.masses.volume("Concrete", density=2400), etc. Returns lightweight definition dataclasses that reference labels, not raw tags. - Resolve (post-mesh, automatic):
get_fem_data()resolves every definition against the mesh and attaches the results to thefem.nodes/fem.elementscomposites (.constraints,.loads,.masses).
The resolver is pure NumPy math with no Gmsh dependency — solver bridges consume the records directly.
Parts¶
A Part owns an isolated Gmsh session, builds a shape, and exports it
to STEP. The session-level g.parts registry imports those STEPs back
into the assembly session, tracks which tags belong to which label,
and offers higher-level operations (fragment_all, fuse_group,
build_node_map, build_face_map) that keep working through
fragmentation and re-tagging.
from apeGmsh import apeGmsh, Part
web = Part("web")
web.begin()
# ... web.model.geometry.add_... to build the shape
web.save("web.step")
web.end()
with apeGmsh(model_name="I_beam") as g:
g.parts.import_step("web.step", label="web")
g.parts.import_step("flange.step", label="top_flange",
translate=(0, 0, 200))
g.parts.import_step("flange.step", label="bot_flange")
g.parts.fragment_all() # conformal interfaces
g.mesh.generation.generate(dim=3)
g.mesh.partitioning.renumber(dim=3, method="rcm", base=1)
fem = g.mesh.queries.get_fem_data(dim=3)
Project layout¶
apeGmsh/
pyproject.toml
README.md
CHANGELOG.md
docs/ # mkdocs site source (index, api/, changelog)
internal_docs/ # authored guides, migration, plans
MIGRATION_v1.md
guide_basics.md
guide_meshing.md
guide_cad_import.md
guide_fem_broker.md
guide_parts_assembly.md
guide_parts_vs_session.md
guide_selection.md
architecture/ # design notes (surfaced in docs site)
examples/ # runnable notebooks and scripts
tests/ # pytest suite (no Gmsh required)
src/
apeGmsh/
__init__.py
_core.py # apeGmsh session class
_session.py # _SessionBase + composite wiring
core/
Model.py # g.model composite container
_model_geometry.py # g.model.geometry
_model_boolean.py # g.model.boolean
_model_transforms.py # g.model.transforms
_model_io.py # g.model.io
_model_queries.py # g.model.queries
Part.py
_parts_registry.py
ConstraintsComposite.py
LoadsComposite.py
MassesComposite.py
mesh/
Mesh.py # g.mesh composite container
_mesh_generation.py # g.mesh.generation
_mesh_sizing.py # g.mesh.sizing
_mesh_field.py # g.mesh.field (FieldHelper)
_mesh_structured.py # g.mesh.structured
_mesh_editing.py # g.mesh.editing
_mesh_queries.py # g.mesh.queries
_mesh_partitioning.py # g.mesh.partitioning
_mesh_algorithms.py # Algorithm2D/3D, OptimizeMethod
FEMData.py
PhysicalGroups.py
MeshSelectionSet.py
MshLoader.py
Partition.py
solvers/
Constraints.py
Loads.py
Masses.py
Numberer.py
opensees/ # apeSees(fem) post-session bridge (Phase 8+)
apesees.py # apeSees class — explicit-constructor entry point
_internal/ # typed primitive namespaces (nDMaterial, element, …)
pattern/ # Plain, UniformExcitation context-managers
emitter/ # tcl / py / h5 / run emitters + h5_reader
viewers/ # PyQt/PyVista viewers
results/ # VTU export + Results container
viz/ # Selection, Inspect, Plot, VTKExport
Viewers¶
apeGmsh ships two separate viewers because pre-solve inspection and post-solve visualization have different requirements. Both use PyVista + Qt, but they target different stages of the workflow.
| Viewer | Import | Use when | Data source |
|---|---|---|---|
| Embedded viewers | g.model.viewer(), g.mesh.viewer(), g.mesh.results_viewer() |
Authoring a model — live inspection while you build geometry, mesh, BCs, loads. | In-process session state (g) + its FEMData broker. Overlays for labels, physical groups, constraints, loads, BCs. |
| Standalone viewer | from apeGmshViewer import show; show(...) — or python -m apeGmshViewer file.vtu |
Reviewing solver output — VTU/PVD files from an OpenSees run, or a MeshData built from Results.to_mesh_data(). |
On-disk files, or a MeshData object (in-process only). |
The standalone viewer auto-detects Jupyter and runs in a subprocess so the notebook keeps its kernel; in scripts it runs blocking.
What's new in viewers (v1.5.0): applied-loads and reactions diagrams, per-card Apply (each diagram layer commits independently), and a per-Geometry display fix so only the active geometry renders.
Source layout:
src/apeGmsh/viewers/— embedded viewers (ModelViewer,MeshViewer, results viewer). Coupled to theapeGmshsession.apeGmshViewer/— standalone post-processing app (MainWindow+ panels + VTU/PVD/MSH loaders). No session dependency.
Examples¶
See the examples/ directory — every notebook runs in-place after
pip install -e .. Recommended order for learning the API:
examples/example_plate_basic.ipynb— minimal plate workflowexamples/example_ibeam_modal.ipynb— I-beam modal analysisexamples/01_embedded_rebars.ipynb— embedded 1D rebars in a concrete volumeexamples/example_frame3D_slab.ipynb— mixed 1D frame + 2D slabexamples/example_gusset.ipynb— imported CAD gusset plate
Migrating from v0.x¶
See internal_docs/MIGRATION_v1.md for the full
checklist. In short: package rename (pyGmsh → apeGmsh), g.model.*
split into five sub-composites, g.mesh.* split into seven,
g.mass → g.masses, g.initialize/finalize → g.begin/end. The
in-session g.opensees.* composite was removed in Phase 8 (ADR
0009) — replace it with from apeGmsh.opensees import apeSees; ops =
apeSees(fem). The migration guide ships a ~150-line Python script that
handles every mechanical rewrite on an existing project.
Credits¶
Developed by: Nicolás Mora Bowen · Patricio Palacios · José Abell · Guppi
Part of José Abell's El Ladruño Research Group.
Credits¶
Developed by: Nicolás Mora Bowen · Patricio Palacios · José Abell · Guppi
Part of José Abell's El Ladruño Research Group.