Mesh — g.mesh
Meshing composite. Seven focused sub-composites.
g.mesh
apeGmsh.mesh.Mesh.Mesh
Mesh(parent: '_ApeGmshSession')
Bases: _HasLogging
Thin composition container for meshing. Every action lives in a
focused sub-composite — see the module docstring for the full map.
Parameters
parent : _SessionBase
Owning session — used to read _verbose and access the
physical composite during _by_physical helpers.
Source code in src/apeGmsh/mesh/Mesh.py
| def __init__(self, parent: "_ApeGmshSession") -> None:
self._parent = parent
# Directive log — records every write-only mesh setting that
# cannot be read back from gmsh (transfinite, setSize, recombine,
# fields, per-entity algorithm, smoothing). Used by
# ``Inspect.print_summary()`` to show what's been applied.
self._directives: list[dict] = []
# Sub-composites — each keeps a reference to self.
self.field = FieldHelper(self)
self.generation = _Generation(self)
self.sizing = _Sizing(self)
self.structured = _Structured(self)
self.editing = _Editing(self)
self.queries = _Queries(self)
self.partitioning = _Partitioning(self)
self.options = _Options(self)
|
viewer
Open the interactive mesh viewer.
The viewer supports picking (BRep entities, elements, nodes),
color modes, and load/constraint/mass overlays.
Parameters are forwarded to :class:MeshViewer.
Source code in src/apeGmsh/mesh/Mesh.py
| def viewer(self, **kwargs):
"""Open the interactive mesh viewer.
The viewer supports picking (BRep entities, elements, nodes),
color modes, and load/constraint/mass overlays.
Parameters are forwarded to :class:`MeshViewer`.
"""
from ..viewers.mesh_viewer import MeshViewer
mv = MeshViewer(self._parent, **kwargs)
return mv.show()
|
preview
preview(*, dims: list[int] | None = None, show_nodes: bool = True, browser: bool = False, return_fig: bool = False)
Interactive WebGL preview of the mesh.
Zero Qt dependency — works inline in Jupyter / VS Code / Colab,
or in a dedicated browser tab when browser=True. Hover over
an element to see its BRep dim and tag; hover over a
node to see its node=N id. Single-click a legend entry to
hide a trace, double-click to isolate it.
Parameters
dims : list of int, optional
Mesh dimensions to render. Defaults to [1, 2, 3] —
surface / volume / 1D curve elements.
show_nodes : bool
Render the full mesh-node cloud as a separate trace
(default True). Matches the Qt mesh viewer, which
always shows the node cloud. Disable for very large meshes
where the nodes overwhelm the element rendering.
browser : bool
If True, open in a new browser tab (temp HTML file)
instead of rendering inline.
return_fig : bool
If True, skip display and return the raw
:class:plotly.graph_objects.Figure.
Source code in src/apeGmsh/mesh/Mesh.py
| def preview(
self,
*,
dims: list[int] | None = None,
show_nodes: bool = True,
browser: bool = False,
return_fig: bool = False,
):
"""Interactive WebGL preview of the mesh.
Zero Qt dependency — works inline in Jupyter / VS Code / Colab,
or in a dedicated browser tab when ``browser=True``. Hover over
an element to see its BRep ``dim`` and ``tag``; hover over a
node to see its ``node=N`` id. Single-click a legend entry to
hide a trace, double-click to isolate it.
Parameters
----------
dims : list of int, optional
Mesh dimensions to render. Defaults to ``[1, 2, 3]`` —
surface / volume / 1D curve elements.
show_nodes : bool
Render the full mesh-node cloud as a separate trace
(default ``True``). Matches the Qt mesh viewer, which
always shows the node cloud. Disable for very large meshes
where the nodes overwhelm the element rendering.
browser : bool
If ``True``, open in a new browser tab (temp HTML file)
instead of rendering inline.
return_fig : bool
If ``True``, skip display and return the raw
:class:`plotly.graph_objects.Figure`.
"""
from ..viz.NotebookPreview import preview_mesh
return preview_mesh(
self._parent,
dims=dims,
show_nodes=show_nodes,
browser=browser,
return_fig=return_fig,
)
|
results_viewer
results_viewer(results: str | None = None, *, point_data: dict | None = None, cell_data: dict | None = None, blocking: bool = False) -> None
Open the results viewer (apeGmshViewer).
Parameters
results : str, optional
Path to a .vtu, .vtk, or .pvd file.
point_data : dict, optional
Nodal fields as numpy arrays: {name: ndarray}.
cell_data : dict, optional
Element fields as numpy arrays: {name: ndarray}.
blocking : bool
If False (default), the viewer runs non-blocking.
Source code in src/apeGmsh/mesh/Mesh.py
| def results_viewer(
self,
results: str | None = None,
*,
point_data: dict | None = None,
cell_data: dict | None = None,
blocking: bool = False,
) -> None:
"""Open the results viewer (apeGmshViewer).
Parameters
----------
results : str, optional
Path to a ``.vtu``, ``.vtk``, or ``.pvd`` file.
point_data : dict, optional
Nodal fields as numpy arrays: ``{name: ndarray}``.
cell_data : dict, optional
Element fields as numpy arrays: ``{name: ndarray}``.
blocking : bool
If False (default), the viewer runs non-blocking.
"""
if results is not None:
from apeGmshViewer import show
show(results, blocking=blocking)
elif point_data is not None or cell_data is not None:
raise NotImplementedError(
"g.mesh.viewer(point_data=..., cell_data=...) was a thin "
"wrapper around the legacy Results class which has been "
"rebuilt. The new flow is being designed as part of the "
"viewer rebuild project — see internal_docs/"
"Results_architecture.md (Phase 9). Until then, call "
"g.mesh.viewer() to show the bare mesh, or pass results=path "
"to load a pre-written .vtu/.pvd file."
)
else:
import tempfile
from apeGmshViewer import show
from ..viz.VTKExport import VTKExport
vtk_export = VTKExport(self._parent)
tmp = tempfile.NamedTemporaryFile(suffix=".vtu", delete=False)
vtk_export.write(tmp.name)
tmp.close()
show(tmp.name, blocking=blocking)
|
Sub-composites
g.mesh.generation
apeGmsh.mesh._mesh_generation._Generation
_Generation(parent_mesh: 'Mesh')
Mesh generation, high-order elevation, refinement, optimisation,
and algorithm selection.
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
generate
generate(dim: int = 3) -> '_Generation'
Generate a mesh up to the given dimension.
Parameters
dim : 1 = edges only, 2 = surface mesh, 3 = volume mesh (default)
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def generate(self, dim: int = 3) -> "_Generation":
"""
Generate a mesh up to the given dimension.
Parameters
----------
dim : 1 = edges only, 2 = surface mesh, 3 = volume mesh (default)
"""
self._validate_pre_mesh()
gmsh.model.mesh.generate(dim)
self._mesh._log(f"generate(dim={dim})")
return self
|
set_order
set_order(order: int, *, bubble: bool = True) -> '_Generation'
Elevate elements to high order.
Parameters
order : 1 = linear, 2 = quadratic, 3 = cubic, …
bubble : include interior (bubble) nodes for order ≥ 2.
True → complete Lagrange (e.g. Q9, T6+bubble).
False → serendipity / incomplete (e.g. Q8, T6).
Global Gmsh flag — applies to the entire mesh.
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def set_order(self, order: int, *, bubble: bool = True) -> "_Generation":
"""
Elevate elements to high order.
Parameters
----------
order : 1 = linear, 2 = quadratic, 3 = cubic, …
bubble : include interior (bubble) nodes for order ≥ 2.
True → complete Lagrange (e.g. Q9, T6+bubble).
False → serendipity / incomplete (e.g. Q8, T6).
Global Gmsh flag — applies to the entire mesh.
"""
if order >= 2:
gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 0 if bubble else 1)
gmsh.model.mesh.setOrder(order)
self._mesh._log(f"set_order({order}, bubble={bubble})")
return self
|
refine
refine() -> '_Generation'
Uniformly refine by splitting every element once.
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def refine(self) -> "_Generation":
"""Uniformly refine by splitting every element once."""
gmsh.model.mesh.refine()
self._mesh._log("refine()")
return self
|
optimize
optimize(method: str = '', *, force: bool = False, niter: int = 1, dim_tags: list[tuple[int, int]] | None = None) -> '_Generation'
Optimise mesh quality.
Parameters
method : one of OptimizeMethod.* constants
force : apply optimisation even to already-valid elements
niter : number of passes
dim_tags : limit to specific entities (None = all)
Example
::
g.mesh.generation.generate(3).optimize(OptimizeMethod.NETGEN, niter=5)
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def optimize(
self,
method : str = "",
*,
force : bool = False,
niter : int = 1,
dim_tags: list[tuple[int, int]] | None = None,
) -> "_Generation":
"""
Optimise mesh quality.
Parameters
----------
method : one of ``OptimizeMethod.*`` constants
force : apply optimisation even to already-valid elements
niter : number of passes
dim_tags : limit to specific entities (``None`` = all)
Example
-------
::
g.mesh.generation.generate(3).optimize(OptimizeMethod.NETGEN, niter=5)
"""
gmsh.model.mesh.optimize(method, force=force, niter=niter,
dimTags=dim_tags or [])
self._mesh._log(f"optimize(method={method!r}, niter={niter})")
return self
|
set_algorithm
set_algorithm(tag, algorithm, *, dim: int = 2) -> '_Generation'
Choose the meshing algorithm for a surface (dim=2) or globally
for all volumes (dim=3).
tag accepts int, label, or PG name. If it resolves to
multiple surfaces, the algorithm is applied to each.
Example
::
g.mesh.generation.set_algorithm("col.web", "frontal_delaunay_quads")
g.mesh.generation.set_algorithm(0, "hxt", dim=3)
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def set_algorithm(
self,
tag,
algorithm,
*,
dim : int = 2,
) -> "_Generation":
"""
Choose the meshing algorithm for a surface (dim=2) or globally
for all volumes (dim=3).
``tag`` accepts int, label, or PG name. If it resolves to
multiple surfaces, the algorithm is applied to each.
Example
-------
::
g.mesh.generation.set_algorithm("col.web", "frontal_delaunay_quads")
g.mesh.generation.set_algorithm(0, "hxt", dim=3)
"""
from apeGmsh.core._helpers import resolve_to_tags
if dim not in (2, 3):
raise ValueError(f"set_algorithm: dim must be 2 or 3, got {dim!r}")
alg_int = _normalize_algorithm(algorithm, dim)
if dim == 2:
tags_resolved = resolve_to_tags(tag, dim=2, session=self._mesh._parent)
for t in tags_resolved:
gmsh.model.mesh.setAlgorithm(2, t, alg_int)
self._mesh._directives.append({
'kind': 'algorithm', 'dim': 2, 'tag': t,
'algorithm': alg_int, 'requested': algorithm,
})
self._mesh._log(
f"set_algorithm(dim=2, tag={t}, alg={algorithm!r} -> {alg_int})"
)
else: # dim == 3
gmsh.option.setNumber("Mesh.Algorithm3D", alg_int)
self._mesh._directives.append({
'kind': 'algorithm', 'dim': 3, 'tag': 0,
'algorithm': alg_int, 'requested': algorithm,
})
self._mesh._log(
f"set_algorithm(dim=3, alg={algorithm!r} -> {alg_int}) [global option]"
)
return self
|
set_algorithm_by_physical
set_algorithm_by_physical(name: str, algorithm, *, dim: int = 2) -> '_Generation'
Deprecated. set_algorithm accepts a PG name directly.
With dim=3 the original wrapper passed tag=0 (since
Mesh.Algorithm3D is a global option); preserve that here.
Source code in src/apeGmsh/mesh/_mesh_generation.py
| def set_algorithm_by_physical(
self,
name : str,
algorithm,
*,
dim : int = 2,
) -> "_Generation":
"""Deprecated. ``set_algorithm`` accepts a PG name directly.
With ``dim=3`` the original wrapper passed tag=0 (since
``Mesh.Algorithm3D`` is a global option); preserve that here.
"""
import warnings
warnings.warn(
"set_algorithm_by_physical is deprecated; pass the "
"physical-group name to set_algorithm() as tag.",
DeprecationWarning,
stacklevel=2,
)
if dim == 3:
return self.set_algorithm(0, algorithm, dim=3)
return self.set_algorithm(name, algorithm, dim=dim)
|
g.mesh.sizing
apeGmsh.mesh._mesh_sizing._Sizing
_Sizing(parent_mesh: 'Mesh')
Global and per-entity element size control.
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
set_global_size
set_global_size(max_size: float, min_size: float = 0.0) -> '_Sizing'
Set the global element-size band.
Parameters
max_size : upper bound assigned to Mesh.MeshSizeMax. Acts
as a ceiling on every size source.
min_size : lower bound assigned to Mesh.MeshSizeMin.
Defaults to 0.0 — i.e. no floor, so per-point
refinements (set_size(..., 100)) are free to
produce elements smaller than max_size.
Example
::
m1.mesh.sizing.set_global_size(6000) # ceiling only
m1.mesh.sizing.set_global_size(6000, 200) # band [200, 6000]
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_global_size(
self,
max_size: float,
min_size: float = 0.0,
) -> "_Sizing":
"""
Set the global element-size band.
Parameters
----------
max_size : upper bound assigned to ``Mesh.MeshSizeMax``. Acts
as a ceiling on every size source.
min_size : lower bound assigned to ``Mesh.MeshSizeMin``.
Defaults to ``0.0`` — i.e. no floor, so per-point
refinements (``set_size(..., 100)``) are free to
produce elements smaller than ``max_size``.
Example
-------
::
m1.mesh.sizing.set_global_size(6000) # ceiling only
m1.mesh.sizing.set_global_size(6000, 200) # band [200, 6000]
"""
gmsh.option.setNumber("Mesh.MeshSizeMax", max_size)
gmsh.option.setNumber("Mesh.MeshSizeMin", min_size)
self._mesh._log(f"set_global_size(max={max_size}, min={min_size})")
return self
|
set_size_sources
set_size_sources(*, from_points: bool | None = None, from_curvature: bool | None = None, extend_from_boundary: bool | None = None) -> '_Sizing'
Control which size sources Gmsh consults when meshing.
Gmsh combines several size sources at each node and takes the
minimum. When from_points is on (Gmsh default), every
BRep point carries its own characteristic length — imported
CAD files typically bake in small lc values that silently
override :meth:set_global_size.
Example
::
(g.mesh.sizing
.set_size_sources(from_points=False,
from_curvature=False,
extend_from_boundary=False)
.set_global_size(6000))
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_size_sources(
self,
*,
from_points : bool | None = None,
from_curvature : bool | None = None,
extend_from_boundary : bool | None = None,
) -> "_Sizing":
"""
Control *which* size sources Gmsh consults when meshing.
Gmsh combines several size sources at each node and takes the
minimum. When ``from_points`` is on (Gmsh default), every
BRep point carries its own characteristic length — imported
CAD files typically bake in small ``lc`` values that silently
override :meth:`set_global_size`.
Example
-------
::
(g.mesh.sizing
.set_size_sources(from_points=False,
from_curvature=False,
extend_from_boundary=False)
.set_global_size(6000))
"""
if from_points is not None:
gmsh.option.setNumber("Mesh.MeshSizeFromPoints", int(bool(from_points)))
if from_curvature is not None:
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", int(bool(from_curvature)))
if extend_from_boundary is not None:
gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary",
int(bool(extend_from_boundary)))
self._mesh._log(
f"set_size_sources(from_points={from_points}, "
f"from_curvature={from_curvature}, "
f"extend_from_boundary={extend_from_boundary})"
)
return self
|
set_size_global
set_size_global(*, min_size: float | None = None, max_size: float | None = None) -> '_Sizing'
Set the global mesh-size bounds independently.
Example
::
g.mesh.sizing.set_size_global(min_size=15, max_size=25)
g.mesh.sizing.set_size_global(max_size=50)
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_size_global(
self,
*,
min_size: float | None = None,
max_size: float | None = None,
) -> "_Sizing":
"""
Set the global mesh-size bounds independently.
Example
-------
::
g.mesh.sizing.set_size_global(min_size=15, max_size=25)
g.mesh.sizing.set_size_global(max_size=50)
"""
if min_size is not None:
gmsh.option.setNumber("Mesh.MeshSizeMin", min_size)
if max_size is not None:
gmsh.option.setNumber("Mesh.MeshSizeMax", max_size)
self._mesh._log(f"set_size_global(min={min_size}, max={max_size})")
return self
|
set_size
set_size(tags, size: float, *, dim: int | None = None) -> '_Sizing'
Assign a target element size by walking to the entity's points.
Gmsh's setSize only honours characteristic lengths on
points (dim=0). This method accepts any reference shape and
recursively walks higher-dimensional entities down to their
BRep points before applying the size — so passing a volume
label, surface PG, or (3, vol_tag) dimtag does what you
expect.
Parameters
tags : int, str, (dim, tag), or list of those
Entity reference. Strings resolve via labels first, then
physical groups, at the entity's native dimension.
size : float
Target characteristic length on the resolved points.
dim : int, optional
Disambiguation hint. Used only when the input is a raw
int tag or when a label exists at multiple dimensions.
Leave as None (default) for label/PG/dimtag inputs.
Example
::
# Per-volume sizing by label (auto-walks to corner points)
g.mesh.sizing.set_size("box", 1.0)
g.mesh.sizing.set_size("box_2", 0.3)
# Direct point list still works
g.mesh.sizing.set_size([p1, p2, p3], 0.05)
# Dimtag form for an explicit volume
g.mesh.sizing.set_size([(3, vol_tag)], 10.0)
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_size(
self,
tags,
size: float,
*,
dim : int | None = None,
) -> "_Sizing":
"""
Assign a target element size by walking to the entity's points.
Gmsh's ``setSize`` only honours characteristic lengths on
points (dim=0). This method accepts any reference shape and
recursively walks higher-dimensional entities down to their
BRep points before applying the size — so passing a volume
label, surface PG, or ``(3, vol_tag)`` dimtag does what you
expect.
Parameters
----------
tags : int, str, (dim, tag), or list of those
Entity reference. Strings resolve via labels first, then
physical groups, at the entity's native dimension.
size : float
Target characteristic length on the resolved points.
dim : int, optional
Disambiguation hint. Used only when the input is a raw
int tag or when a label exists at multiple dimensions.
Leave as ``None`` (default) for label/PG/dimtag inputs.
Example
-------
::
# Per-volume sizing by label (auto-walks to corner points)
g.mesh.sizing.set_size("box", 1.0)
g.mesh.sizing.set_size("box_2", 0.3)
# Direct point list still works
g.mesh.sizing.set_size([p1, p2, p3], 0.05)
# Dimtag form for an explicit volume
g.mesh.sizing.set_size([(3, vol_tag)], 10.0)
"""
from apeGmsh.core._helpers import resolve_to_dimtags
# Resolve to dimtags at native dim (label/PG aware).
# `dim` falls back to 0 only as the raw-int default to
# preserve the historical "bare ints are points" behaviour.
default_dim = 0 if dim is None else dim
dimtags = resolve_to_dimtags(
tags, default_dim=default_dim, session=self._mesh._parent,
)
# Walk any non-point entity to its boundary points.
point_dimtags: list[tuple[int, int]] = []
seen: set[int] = set()
for d, t in dimtags:
if d == 0:
if t not in seen:
seen.add(t)
point_dimtags.append((0, t))
continue
boundary = gmsh.model.getBoundary(
[(d, t)], combined=False, oriented=False, recursive=True,
)
for _, pt in boundary:
pt = abs(int(pt))
if pt not in seen:
seen.add(pt)
point_dimtags.append((0, pt))
if not point_dimtags:
raise ValueError(
f"set_size: no points resolved from {tags!r} "
f"(dimtags={dimtags}). Cannot apply characteristic length."
)
gmsh.model.mesh.setSize(point_dimtags, size)
self._mesh._directives.append({
'kind': 'set_size', 'dim': 0,
'tags': [t for _, t in point_dimtags], 'size': size,
'source_ref': repr(tags),
})
self._mesh._log(
f"set_size(ref={tags!r}, size={size}, "
f"n_points={len(point_dimtags)})"
)
return self
|
set_size_all_points
set_size_all_points(size: float) -> '_Sizing'
Assign the same characteristic length to every BRep point in
the model.
Typical use — normalising per-point lc values after an
IGES/STEP/DXF import.
Example
::
(g.mesh.sizing
.set_size_all_points(6000)
.set_global_size(6000))
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_size_all_points(self, size: float) -> "_Sizing":
"""
Assign the same characteristic length to every BRep point in
the model.
Typical use — normalising per-point ``lc`` values after an
IGES/STEP/DXF import.
Example
-------
::
(g.mesh.sizing
.set_size_all_points(6000)
.set_global_size(6000))
"""
pts = gmsh.model.getEntities(dim=0)
if pts:
gmsh.model.mesh.setSize(pts, size)
self._mesh._directives.append({
'kind': 'set_size_all_points', 'size': size,
'n_points': len(pts),
})
self._mesh._log(f"set_size_all_points(size={size}, n={len(pts)})")
return self
|
set_size_callback
set_size_callback(func: Callable[[float, float, float, float, int, int], float]) -> '_Sizing'
Register a Python callback that returns the desired element
size at any point in the model.
Callback signature func(dim, tag, x, y, z, lc) -> float.
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_size_callback(
self,
func: Callable[[float, float, float, float, int, int], float],
) -> "_Sizing":
"""
Register a Python callback that returns the desired element
size at any point in the model.
Callback signature ``func(dim, tag, x, y, z, lc) -> float``.
"""
gmsh.model.mesh.setSizeCallback(func)
self._mesh._directives.append({
'kind': 'set_size_callback',
'func_name': getattr(func, '__name__', '<callable>'),
})
self._mesh._log("set_size_callback(<callable>)")
return self
|
set_size_by_physical
set_size_by_physical(name: str, size: float, *, dim: int | None = None) -> '_Sizing'
Apply :meth:set_size to every entity in a physical group.
Resolves the PG (optionally restricted to dim), then
delegates to :meth:set_size, which walks higher-dimensional
entities down to their boundary points automatically.
Parameters
name : physical-group name.
size : target characteristic length.
dim : optional dimension filter; None resolves at the
PG's native dim.
Source code in src/apeGmsh/mesh/_mesh_sizing.py
| def set_size_by_physical(
self,
name : str,
size : float,
*,
dim : int | None = None,
) -> "_Sizing":
"""
Apply :meth:`set_size` to every entity in a physical group.
Resolves the PG (optionally restricted to ``dim``), then
delegates to :meth:`set_size`, which walks higher-dimensional
entities down to their boundary points automatically.
Parameters
----------
name : physical-group name.
size : target characteristic length.
dim : optional dimension filter; ``None`` resolves at the
PG's native dim.
"""
# If dim is given, restrict resolution; otherwise let set_size
# auto-resolve via the string path (label-then-PG).
if dim is not None:
tags = self._mesh._resolve_physical(name, dim)
dimtags = [(dim, t) for t in tags]
self._mesh._log(
f"set_size_by_physical(name={name!r}, dim={dim}, "
f"tags={tags}, size={size})"
)
self.set_size(dimtags, size)
else:
self._mesh._log(
f"set_size_by_physical(name={name!r}, size={size})"
)
self.set_size(name, size)
return self
|
g.mesh.field
apeGmsh.mesh._mesh_field.FieldHelper
FieldHelper(parent_mesh: 'Mesh')
Fluent wrapper around gmsh.model.mesh.field.
Accessed via g.mesh.field. Two usage levels:
Raw control (full flexibility)::
f = g.mesh.field.add("Distance")
g.mesh.field.set_numbers(f, "CurvesList", [1, 2, 3])
g.mesh.field.set_background(f)
Convenience builders (common fields with named parameters)::
dist = g.mesh.field.distance(curves=[1, 2])
thr = g.mesh.field.threshold(dist, size_min=0.05, size_max=0.5,
dist_min=0.1, dist_max=1.0)
g.mesh.field.set_background(thr)
Source code in src/apeGmsh/mesh/_mesh_field.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
add
add(field_type: str) -> int
Create a new field of the given type and return its tag.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def add(self, field_type: str) -> int:
"""Create a new field of the given type and return its tag."""
tag = gmsh.model.mesh.field.add(field_type)
self._mesh._directives.append({
'kind': 'field_add', 'field_type': field_type, 'field_tag': tag,
})
self._log(f"add({field_type!r}) -> field tag {tag}")
return tag
|
set_number
set_number(tag: int, name: str, value: float) -> 'FieldHelper'
Set a scalar parameter on a field.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def set_number(self, tag: int, name: str, value: float) -> "FieldHelper":
"""Set a scalar parameter on a field."""
gmsh.model.mesh.field.setNumber(tag, name, value)
return self
|
set_numbers
set_numbers(tag: int, name: str, values: list[float]) -> 'FieldHelper'
Set a list parameter on a field.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def set_numbers(self, tag: int, name: str, values: list[float]) -> "FieldHelper":
"""Set a list parameter on a field."""
gmsh.model.mesh.field.setNumbers(tag, name, values)
return self
|
set_string
set_string(tag: int, name: str, value: str) -> 'FieldHelper'
Set a string parameter on a field.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def set_string(self, tag: int, name: str, value: str) -> "FieldHelper":
"""Set a string parameter on a field."""
gmsh.model.mesh.field.setString(tag, name, value)
return self
|
set_background
set_background(tag: int) -> 'FieldHelper'
Register a field as the global background mesh size.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def set_background(self, tag: int) -> "FieldHelper":
"""Register a field as the global background mesh size."""
gmsh.model.mesh.field.setAsBackgroundMesh(tag)
self._mesh._directives.append({
'kind': 'field_background', 'field_tag': tag,
})
self._log(f"set_background(field={tag})")
return self
|
set_boundary_layer_field
set_boundary_layer_field(tag: int) -> 'FieldHelper'
Register a BoundaryLayer field to be applied during meshing.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def set_boundary_layer_field(self, tag: int) -> "FieldHelper":
"""Register a BoundaryLayer field to be applied during meshing."""
gmsh.model.mesh.field.setAsBoundaryLayer(tag)
self._log(f"set_boundary_layer_field(field={tag})")
return self
|
distance
distance(*, curves=None, surfaces=None, points=None, sampling: int = 100) -> int
Create a Distance field measuring shortest distance to entities.
Each of curves, surfaces, points accepts an int, a
label or PG name, a (dim, tag) tuple, or a list of any mix.
Refs are validated against the expected dimension.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def distance(
self,
*,
curves = None,
surfaces = None,
points = None,
sampling: int = 100,
) -> int:
"""Create a ``Distance`` field measuring shortest distance to entities.
Each of ``curves``, ``surfaces``, ``points`` accepts an int, a
label or PG name, a ``(dim, tag)`` tuple, or a list of any mix.
Refs are validated against the expected dimension.
"""
curve_tags = self._resolve_at_dim(curves, 1, "distance(curves=)")
surf_tags = self._resolve_at_dim(surfaces, 2, "distance(surfaces=)")
point_tags = self._resolve_at_dim(points, 0, "distance(points=)")
tag = gmsh.model.mesh.field.add("Distance")
if curve_tags:
gmsh.model.mesh.field.setNumbers(tag, "CurvesList", curve_tags)
if surf_tags:
gmsh.model.mesh.field.setNumbers(tag, "SurfacesList", surf_tags)
if point_tags:
gmsh.model.mesh.field.setNumbers(tag, "PointsList", point_tags)
gmsh.model.mesh.field.setNumber(tag, "Sampling", sampling)
self._log(
f"distance(curves={curve_tags!r}, surfaces={surf_tags!r}, "
f"points={point_tags!r}) -> field {tag}"
)
return tag
|
threshold
threshold(distance_field: int, *, size_min: float, size_max: float, dist_min: float, dist_max: float, sigmoid: bool = False, stop_at_dist_max: bool = False) -> int
Create a Threshold field ramping size from size_min to size_max.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def threshold(
self,
distance_field : int,
*,
size_min : float,
size_max : float,
dist_min : float,
dist_max : float,
sigmoid : bool = False,
stop_at_dist_max: bool = False,
) -> int:
"""Create a ``Threshold`` field ramping size from size_min to size_max."""
tag = gmsh.model.mesh.field.add("Threshold")
gmsh.model.mesh.field.setNumber(tag, "InField", distance_field)
gmsh.model.mesh.field.setNumber(tag, "SizeMin", size_min)
gmsh.model.mesh.field.setNumber(tag, "SizeMax", size_max)
gmsh.model.mesh.field.setNumber(tag, "DistMin", dist_min)
gmsh.model.mesh.field.setNumber(tag, "DistMax", dist_max)
gmsh.model.mesh.field.setNumber(tag, "Sigmoid", int(sigmoid))
gmsh.model.mesh.field.setNumber(tag, "StopAtDistMax", int(stop_at_dist_max))
self._log(
f"threshold(in={distance_field}, "
f"size=[{size_min},{size_max}], "
f"dist=[{dist_min},{dist_max}]) -> field {tag}"
)
return tag
|
math_eval
math_eval(expression: str) -> int
Create a MathEval field using an expression in x, y, z.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def math_eval(self, expression: str) -> int:
"""Create a ``MathEval`` field using an expression in x, y, z."""
tag = gmsh.model.mesh.field.add("MathEval")
gmsh.model.mesh.field.setString(tag, "F", expression)
self._log(f"math_eval({expression!r}) -> field {tag}")
return tag
|
box
box(*, x_min: float, y_min: float, z_min: float, x_max: float, y_max: float, z_max: float, size_in: float, size_out: float, thickness: float = 0.0) -> int
Create a Box field: size_in inside, size_out outside.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def box(
self,
*,
x_min : float,
y_min : float,
z_min : float,
x_max : float,
y_max : float,
z_max : float,
size_in : float,
size_out : float,
thickness: float = 0.0,
) -> int:
"""Create a ``Box`` field: size_in inside, size_out outside."""
tag = gmsh.model.mesh.field.add("Box")
gmsh.model.mesh.field.setNumber(tag, "VIn", size_in)
gmsh.model.mesh.field.setNumber(tag, "VOut", size_out)
gmsh.model.mesh.field.setNumber(tag, "XMin", x_min)
gmsh.model.mesh.field.setNumber(tag, "YMin", y_min)
gmsh.model.mesh.field.setNumber(tag, "ZMin", z_min)
gmsh.model.mesh.field.setNumber(tag, "XMax", x_max)
gmsh.model.mesh.field.setNumber(tag, "YMax", y_max)
gmsh.model.mesh.field.setNumber(tag, "ZMax", z_max)
if thickness > 0.0:
gmsh.model.mesh.field.setNumber(tag, "Thickness", thickness)
self._log(
f"box(size_in={size_in}, size_out={size_out}, "
f"x=[{x_min},{x_max}], y=[{y_min},{y_max}], "
f"z=[{z_min},{z_max}]) -> field {tag}"
)
return tag
|
minimum
minimum(field_tags: list[int]) -> int
Create a Min field — element-wise minimum of several fields.
Source code in src/apeGmsh/mesh/_mesh_field.py
| def minimum(self, field_tags: list[int]) -> int:
"""Create a ``Min`` field — element-wise minimum of several fields."""
tag = gmsh.model.mesh.field.add("Min")
gmsh.model.mesh.field.setNumbers(tag, "FieldsList", field_tags)
self._log(f"minimum({field_tags}) -> field {tag}")
return tag
|
boundary_layer
boundary_layer(*, curves=None, points=None, size_near: float, ratio: float = 1.2, n_layers: int = 5, thickness: float | None = None, fan_points=None) -> int
Create a BoundaryLayer field for wall-resolved meshes.
curves, points, and fan_points accept any flexible
reference shape (int / label / PG / (dim, tag) / list).
Source code in src/apeGmsh/mesh/_mesh_field.py
| def boundary_layer(
self,
*,
curves = None,
points = None,
size_near : float,
ratio : float = 1.2,
n_layers : int = 5,
thickness : float | None = None,
fan_points = None,
) -> int:
"""Create a ``BoundaryLayer`` field for wall-resolved meshes.
``curves``, ``points``, and ``fan_points`` accept any flexible
reference shape (int / label / PG / ``(dim, tag)`` / list).
"""
curve_tags = self._resolve_at_dim(curves, 1, "boundary_layer(curves=)")
point_tags = self._resolve_at_dim(points, 0, "boundary_layer(points=)")
fan_tags = self._resolve_at_dim(fan_points, 0,
"boundary_layer(fan_points=)")
tag = gmsh.model.mesh.field.add("BoundaryLayer")
if curve_tags:
gmsh.model.mesh.field.setNumbers(tag, "CurvesList", curve_tags)
if point_tags:
gmsh.model.mesh.field.setNumbers(tag, "PointsList", point_tags)
if fan_tags:
gmsh.model.mesh.field.setNumbers(tag, "FanPointsList", fan_tags)
gmsh.model.mesh.field.setNumber(tag, "Size", size_near)
gmsh.model.mesh.field.setNumber(tag, "Ratio", ratio)
gmsh.model.mesh.field.setNumber(tag, "NbLayers", n_layers)
if thickness is not None:
gmsh.model.mesh.field.setNumber(tag, "Thickness", thickness)
self._log(
f"boundary_layer(size={size_near}, ratio={ratio}, "
f"layers={n_layers}) -> field {tag}"
)
return tag
|
g.mesh.options
apeGmsh.mesh._mesh_options._Options
_Options(parent_mesh: 'Mesh')
Global Gmsh mesher options.
Each set_* method maps to one gmsh.option.setNumber("Mesh.X", v)
call but adds string-enum input, validation, and logging. Methods
return self for chaining.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
set_subdivision_algorithm
set_subdivision_algorithm(algorithm) -> '_Options'
Post-process tets/prisms into hexes after generation.
Maps to Mesh.SubdivisionAlgorithm.
Parameters
algorithm : str or int
One of:
- ``"none"`` (0) no subdivision (default)
- ``"all_quad"`` (1) split tris into 3 quads each
- ``"all_hex"`` (2) split tets/prisms into hexes
Notes
Redundant for transfinite + face-recombined volumes — those
produce hexes natively via the structured mesher. This option
is for the unstructured-tet → hex conversion path.
Example
::
g.mesh.options.set_subdivision_algorithm("all_hex")
Source code in src/apeGmsh/mesh/_mesh_options.py
| def set_subdivision_algorithm(self, algorithm) -> "_Options":
"""Post-process tets/prisms into hexes after generation.
Maps to ``Mesh.SubdivisionAlgorithm``.
Parameters
----------
algorithm : str or int
One of:
- ``"none"`` (0) no subdivision (default)
- ``"all_quad"`` (1) split tris into 3 quads each
- ``"all_hex"`` (2) split tets/prisms into hexes
Notes
-----
Redundant for transfinite + face-recombined volumes — those
produce hexes natively via the structured mesher. This option
is for the unstructured-tet → hex conversion path.
Example
-------
::
g.mesh.options.set_subdivision_algorithm("all_hex")
"""
code = _resolve_enum(algorithm, _SUBDIVISION_ALGORITHM,
"set_subdivision_algorithm")
gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", code)
self._mesh._log(f"options.set_subdivision_algorithm({algorithm!r})")
return self
|
get_subdivision_algorithm
get_subdivision_algorithm()
Return the current Mesh.SubdivisionAlgorithm as a string or int.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def get_subdivision_algorithm(self):
"""Return the current ``Mesh.SubdivisionAlgorithm`` as a string or int."""
return _enum_string_or_int(
int(gmsh.option.getNumber("Mesh.SubdivisionAlgorithm")),
_SUBDIVISION_ALGORITHM,
)
|
set_smoothing
set_smoothing(iterations: int) -> '_Options'
Number of global Laplacian smoothing passes applied after generation.
Maps to Mesh.Smoothing.
Parameters
iterations : int
Number of smoothing passes. 0 disables. Higher values
improve element quality on unstructured meshes; on
transfinite meshes the option is effectively a no-op since
interior nodes are already on the structured grid.
See Also
g.mesh.structured.set_smoothing :
Per-entity smoothing constraint (different — sets
gmsh.model.mesh.setSmoothing on a specific tag).
Source code in src/apeGmsh/mesh/_mesh_options.py
| def set_smoothing(self, iterations: int) -> "_Options":
"""Number of global Laplacian smoothing passes applied after generation.
Maps to ``Mesh.Smoothing``.
Parameters
----------
iterations : int
Number of smoothing passes. ``0`` disables. Higher values
improve element quality on unstructured meshes; on
transfinite meshes the option is effectively a no-op since
interior nodes are already on the structured grid.
See Also
--------
g.mesh.structured.set_smoothing :
Per-entity smoothing constraint (different — sets
``gmsh.model.mesh.setSmoothing`` on a specific tag).
"""
gmsh.option.setNumber("Mesh.Smoothing", int(iterations))
self._mesh._log(f"options.set_smoothing(iterations={iterations})")
return self
|
get_smoothing
Return the current Mesh.Smoothing value.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def get_smoothing(self) -> int:
"""Return the current ``Mesh.Smoothing`` value."""
return int(gmsh.option.getNumber("Mesh.Smoothing"))
|
set_element_order
set_element_order(order: int) -> '_Options'
Element interpolation order.
Maps to Mesh.ElementOrder.
Parameters
order : int
1 for linear elements (default), 2 for quadratic,
higher orders supported but rarely used. Higher-order
meshes have additional mid-edge / mid-face nodes; downstream
FEM code must support the element type.
Example
::
g.mesh.options.set_element_order(2) # quadratic hexes/tets
Source code in src/apeGmsh/mesh/_mesh_options.py
| def set_element_order(self, order: int) -> "_Options":
"""Element interpolation order.
Maps to ``Mesh.ElementOrder``.
Parameters
----------
order : int
``1`` for linear elements (default), ``2`` for quadratic,
higher orders supported but rarely used. Higher-order
meshes have additional mid-edge / mid-face nodes; downstream
FEM code must support the element type.
Example
-------
::
g.mesh.options.set_element_order(2) # quadratic hexes/tets
"""
gmsh.option.setNumber("Mesh.ElementOrder", int(order))
self._mesh._log(f"options.set_element_order({order})")
return self
|
get_element_order
get_element_order() -> int
Return the current Mesh.ElementOrder value.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def get_element_order(self) -> int:
"""Return the current ``Mesh.ElementOrder`` value."""
return int(gmsh.option.getNumber("Mesh.ElementOrder"))
|
set_algorithm_2d
set_algorithm_2d(algorithm) -> '_Options'
2-D meshing algorithm.
Maps to Mesh.Algorithm.
Parameters
algorithm : str or int
One of:
- ``"meshadapt"`` (1) adaptive Delaunay
- ``"automatic"`` (2) default — let Gmsh choose
- ``"delaunay"`` (5) pure Delaunay
- ``"frontal"`` (6) frontal-Delaunay
- ``"bamg"`` (7) anisotropic remeshing
- ``"frontal_quads"`` (8) frontal-Delaunay for quads
- ``"packing"`` (9) packing of parallelograms
- ``"quasi_structured_quad"`` (11) quasi-structured quads
(Gmsh ≥ 4.10)
Source code in src/apeGmsh/mesh/_mesh_options.py
| def set_algorithm_2d(self, algorithm) -> "_Options":
"""2-D meshing algorithm.
Maps to ``Mesh.Algorithm``.
Parameters
----------
algorithm : str or int
One of:
- ``"meshadapt"`` (1) adaptive Delaunay
- ``"automatic"`` (2) default — let Gmsh choose
- ``"delaunay"`` (5) pure Delaunay
- ``"frontal"`` (6) frontal-Delaunay
- ``"bamg"`` (7) anisotropic remeshing
- ``"frontal_quads"`` (8) frontal-Delaunay for quads
- ``"packing"`` (9) packing of parallelograms
- ``"quasi_structured_quad"`` (11) quasi-structured quads
(Gmsh ≥ 4.10)
"""
code = _resolve_enum(algorithm, _ALGORITHM_2D, "set_algorithm_2d")
gmsh.option.setNumber("Mesh.Algorithm", code)
self._mesh._log(f"options.set_algorithm_2d({algorithm!r})")
return self
|
get_algorithm_2d
Return the current Mesh.Algorithm as a string or int.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def get_algorithm_2d(self):
"""Return the current ``Mesh.Algorithm`` as a string or int."""
return _enum_string_or_int(
int(gmsh.option.getNumber("Mesh.Algorithm")),
_ALGORITHM_2D,
)
|
set_algorithm_3d
set_algorithm_3d(algorithm) -> '_Options'
3-D meshing algorithm.
Maps to Mesh.Algorithm3D.
Parameters
algorithm : str or int
One of:
- ``"delaunay"`` (1) pure Delaunay (default)
- ``"frontal"`` (4) frontal
- ``"frontal_delaunay"`` (5) frontal-Delaunay
- ``"mmg3d"`` (7) anisotropic remeshing
- ``"rtree"`` (9) R-tree based
- ``"hxt"`` (10) HXT — much faster than Delaunay
on large unstructured tet meshes
Source code in src/apeGmsh/mesh/_mesh_options.py
| def set_algorithm_3d(self, algorithm) -> "_Options":
"""3-D meshing algorithm.
Maps to ``Mesh.Algorithm3D``.
Parameters
----------
algorithm : str or int
One of:
- ``"delaunay"`` (1) pure Delaunay (default)
- ``"frontal"`` (4) frontal
- ``"frontal_delaunay"`` (5) frontal-Delaunay
- ``"mmg3d"`` (7) anisotropic remeshing
- ``"rtree"`` (9) R-tree based
- ``"hxt"`` (10) HXT — much faster than Delaunay
on large unstructured tet meshes
"""
code = _resolve_enum(algorithm, _ALGORITHM_3D, "set_algorithm_3d")
gmsh.option.setNumber("Mesh.Algorithm3D", code)
self._mesh._log(f"options.set_algorithm_3d({algorithm!r})")
return self
|
get_algorithm_3d
Return the current Mesh.Algorithm3D as a string or int.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def get_algorithm_3d(self):
"""Return the current ``Mesh.Algorithm3D`` as a string or int."""
return _enum_string_or_int(
int(gmsh.option.getNumber("Mesh.Algorithm3D")),
_ALGORITHM_3D,
)
|
set_recombination_algorithm
set_recombination_algorithm(algorithm) -> '_Options'
Strategy used when recombining triangles into quads.
Maps to Mesh.RecombinationAlgorithm.
Parameters
algorithm : str or int
One of:
- ``"simple"`` (0) simple
- ``"blossom"`` (1) Blossom — better quality, default
- ``"simple_full"`` (2) simple, full recombination
- ``"blossom_full"`` (3) Blossom, full recombination —
highest quality for all-quad meshes
Source code in src/apeGmsh/mesh/_mesh_options.py
| def set_recombination_algorithm(self, algorithm) -> "_Options":
"""Strategy used when recombining triangles into quads.
Maps to ``Mesh.RecombinationAlgorithm``.
Parameters
----------
algorithm : str or int
One of:
- ``"simple"`` (0) simple
- ``"blossom"`` (1) Blossom — better quality, default
- ``"simple_full"`` (2) simple, full recombination
- ``"blossom_full"`` (3) Blossom, full recombination —
highest quality for all-quad meshes
"""
code = _resolve_enum(algorithm, _RECOMBINATION_ALGORITHM,
"set_recombination_algorithm")
gmsh.option.setNumber("Mesh.RecombinationAlgorithm", code)
self._mesh._log(f"options.set_recombination_algorithm({algorithm!r})")
return self
|
get_recombination_algorithm
get_recombination_algorithm()
Return the current Mesh.RecombinationAlgorithm as a string or int.
Source code in src/apeGmsh/mesh/_mesh_options.py
| def get_recombination_algorithm(self):
"""Return the current ``Mesh.RecombinationAlgorithm`` as a string or int."""
return _enum_string_or_int(
int(gmsh.option.getNumber("Mesh.RecombinationAlgorithm")),
_RECOMBINATION_ALGORITHM,
)
|
Common recipes
# All-hex output for an unstructured tet model
g.mesh.options.set_subdivision_algorithm("all_hex")
# Quadratic elements (8-node hex → 27-node hex, 4-node tet → 10-node tet)
g.mesh.options.set_element_order(2)
# HXT algorithm — much faster than Delaunay for large unstructured tet meshes
g.mesh.options.set_algorithm_3d("hxt")
# Highest-quality all-quad surface mesh
g.mesh.options.set_recombination_algorithm("blossom_full")
g.mesh.options.set_algorithm_2d("frontal_quads")
# Fluent chaining — every setter returns self
(g.mesh.options
.set_algorithm_3d("hxt")
.set_element_order(2)
.set_smoothing(iterations=3))
For options not wrapped here, drop to gmsh.option.setNumber("Mesh.X", v)
directly. The wrapper covers the ~6 options users actually tune; Gmsh has
~50+ Mesh.* options total.
g.mesh.structured
apeGmsh.mesh._mesh_structured._Structured
_Structured(parent_mesh: 'Mesh')
Transfinite constraints, recombination, smoothing, compounds.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
set_transfinite_curve
set_transfinite_curve(tag, n_nodes: int, *, mesh_type: str = 'Progression', coef: float = 1.0) -> '_Structured'
Force a curve to be meshed with a deterministic node count and distribution.
A "transfinite" curve has its nodes placed by formula rather than by
the unstructured mesher. This is the building block for structured
meshes: when every bounding curve of a surface is transfinite, the
surface itself can be made transfinite (a structured grid); same for
volumes built from transfinite surfaces.
Parameters
tag :
Curve identifier. Accepts an int tag, a label string, a
physical-group name, a (1, tag) dimtag, or a list of any
of these (constraint applied to each). Works with
:meth:Selection.tags() <apeGmsh.core._selection.Selection.tags>
output.
n_nodes : int
Number of nodes along the curve (≥ 2). n_nodes - 1
elements are produced.
mesh_type : str, default "Progression"
Node distribution rule. One of:
- ``"Progression"`` — geometric progression. ``coef`` is
the ratio between successive intervals.
``coef = 1`` ⇒ uniform; ``coef > 1`` clusters nodes
toward the curve's end point; ``coef < 1`` clusters
toward the start.
- ``"Bump"`` — symmetric biasing. ``coef > 1`` clusters
toward the middle; ``coef < 1`` clusters toward both
ends; ``coef = 1`` ⇒ uniform.
- ``"Beta"`` — Beta-distribution biasing (Gmsh ≥ 4.10).
coef : float, default 1.0
Distribution parameter — meaning depends on mesh_type
(see above). Use 1.0 for a uniform distribution.
Returns
_Structured
self for chaining.
Notes
The constraint is only honored if the curve is part of a
transfinite surface (and the surface part of a transfinite volume,
for 3-D meshes). A transfinite curve in isolation will simply
seed the unstructured mesher with that node count.
Curve direction matters for non-uniform distributions: which
end is "start" vs "end" follows the curve's intrinsic orientation
in Gmsh. Reverse the orientation (or pass coef = 1/r instead
of r) to flip the clustering.
Examples
Uniform 11-node spacing on a single curve::
m.mesh.structured.set_transfinite_curve(curve_tag, n_nodes=11)
Geometric refinement toward the curve's end (boundary-layer style)::
m.mesh.structured.set_transfinite_curve(
curve_tag, n_nodes=21,
mesh_type="Progression", coef=1.1,
)
Symmetric refinement toward the middle (capture a feature at mid-span)::
m.mesh.structured.set_transfinite_curve(
curve_tag, n_nodes=21,
mesh_type="Bump", coef=0.25,
)
Pass a Selection's tags to constrain many curves at once::
edges = m.model.select("box", dim=1).result()
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("z").tags(),
n_nodes=21,
)
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite_curve(
self,
tag,
n_nodes : int,
*,
mesh_type: str = "Progression",
coef : float = 1.0,
) -> "_Structured":
"""Force a curve to be meshed with a deterministic node count and distribution.
A "transfinite" curve has its nodes placed by formula rather than by
the unstructured mesher. This is the building block for structured
meshes: when every bounding curve of a surface is transfinite, the
surface itself can be made transfinite (a structured grid); same for
volumes built from transfinite surfaces.
Parameters
----------
tag :
Curve identifier. Accepts an int tag, a label string, a
physical-group name, a ``(1, tag)`` dimtag, or a list of any
of these (constraint applied to each). Works with
:meth:`Selection.tags() <apeGmsh.core._selection.Selection.tags>`
output.
n_nodes : int
Number of nodes along the curve (≥ 2). ``n_nodes - 1``
elements are produced.
mesh_type : str, default ``"Progression"``
Node distribution rule. One of:
- ``"Progression"`` — geometric progression. ``coef`` is
the ratio between successive intervals.
``coef = 1`` ⇒ uniform; ``coef > 1`` clusters nodes
toward the curve's end point; ``coef < 1`` clusters
toward the start.
- ``"Bump"`` — symmetric biasing. ``coef > 1`` clusters
toward the middle; ``coef < 1`` clusters toward both
ends; ``coef = 1`` ⇒ uniform.
- ``"Beta"`` — Beta-distribution biasing (Gmsh ≥ 4.10).
coef : float, default ``1.0``
Distribution parameter — meaning depends on ``mesh_type``
(see above). Use ``1.0`` for a uniform distribution.
Returns
-------
_Structured
``self`` for chaining.
Notes
-----
The constraint is only honored if the curve is part of a
transfinite surface (and the surface part of a transfinite volume,
for 3-D meshes). A transfinite curve in isolation will simply
seed the unstructured mesher with that node count.
Curve **direction** matters for non-uniform distributions: which
end is "start" vs "end" follows the curve's intrinsic orientation
in Gmsh. Reverse the orientation (or pass ``coef = 1/r`` instead
of ``r``) to flip the clustering.
Examples
--------
Uniform 11-node spacing on a single curve::
m.mesh.structured.set_transfinite_curve(curve_tag, n_nodes=11)
Geometric refinement toward the curve's end (boundary-layer style)::
m.mesh.structured.set_transfinite_curve(
curve_tag, n_nodes=21,
mesh_type="Progression", coef=1.1,
)
Symmetric refinement toward the middle (capture a feature at mid-span)::
m.mesh.structured.set_transfinite_curve(
curve_tag, n_nodes=21,
mesh_type="Bump", coef=0.25,
)
Pass a Selection's tags to constrain many curves at once::
edges = m.model.select("box", dim=1).result()
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("z").tags(),
n_nodes=21,
)
"""
for t in self._resolve(tag, dim=1):
gmsh.model.mesh.setTransfiniteCurve(t, n_nodes,
meshType=mesh_type, coef=coef)
self._mesh._directives.append({
'kind': 'transfinite_curve', 'tag': t,
'n_nodes': n_nodes, 'mesh_type': mesh_type, 'coef': coef,
})
self._mesh._log(
f"set_transfinite_curve(tag={t}, n={n_nodes}, "
f"type={mesh_type!r}, coef={coef})"
)
return self
|
set_transfinite_surface
set_transfinite_surface(tag, *, arrangement: str = 'Left', corners: list[int] | None = None) -> '_Structured'
Force a surface to be meshed as a structured grid.
A transfinite surface has its interior nodes laid out by transfinite
interpolation between its bounding curves. Combined with transfinite
curves on every bounding edge, this produces a fully structured
surface mesh (triangles by default, quads with
:meth:set_recombine).
Parameters
tag :
Surface identifier. Accepts an int tag, label string,
physical-group name, (2, tag) dimtag, or list of any.
arrangement : str, default "Left"
Diagonal direction for the structured triangles. Ignored
once the surface is recombined to quads. Values:
- ``"Left"`` all diagonals slant the same way
- ``"Right"`` all diagonals slant the other way
- ``"AlternateLeft"`` alternating pattern, starting Left
- ``"AlternateRight"`` alternating pattern, starting Right
corners : list[int] | None, default None
Tags of the 3 or 4 corner points defining the structured
topology. Required when the surface has more than 4
bounding curves (e.g. after a face split) or when Gmsh
can't auto-detect the corners. Pass None for a clean
3- or 4-sided face.
Returns
_Structured
self for chaining.
Prerequisites
Every bounding curve of the surface must already be transfinite
(see :meth:set_transfinite_curve). Opposite edges must have
matching n_nodes (or the mesher will fail at generation time
with a "transfinite surface: inconsistent number of nodes" error).
Examples
Clean rectangle::
m.mesh.structured.set_transfinite_surface(face_tag)
Surface with 5+ bounding curves — pick the 4 logical corners::
m.mesh.structured.set_transfinite_surface(
face_tag, corners=[p1, p2, p3, p4],
)
Apply to every horizontal face of a layer::
faces = m.model.select("layer_1", dim=2).result()
m.mesh.structured.set_transfinite_surface(
faces.normal_along("z").tags(),
)
Notes
For a quad/hex mesh, follow with :meth:set_recombine on the
same surface. The all-in-one helper :meth:set_transfinite_box
does this automatically for clean hex volumes.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite_surface(
self,
tag,
*,
arrangement: str = "Left",
corners : list[int] | None = None,
) -> "_Structured":
"""Force a surface to be meshed as a structured grid.
A transfinite surface has its interior nodes laid out by transfinite
interpolation between its bounding curves. Combined with transfinite
curves on every bounding edge, this produces a fully structured
surface mesh (triangles by default, quads with
:meth:`set_recombine`).
Parameters
----------
tag :
Surface identifier. Accepts an int tag, label string,
physical-group name, ``(2, tag)`` dimtag, or list of any.
arrangement : str, default ``"Left"``
Diagonal direction for the structured triangles. Ignored
once the surface is recombined to quads. Values:
- ``"Left"`` all diagonals slant the same way
- ``"Right"`` all diagonals slant the other way
- ``"AlternateLeft"`` alternating pattern, starting Left
- ``"AlternateRight"`` alternating pattern, starting Right
corners : list[int] | None, default ``None``
Tags of the 3 or 4 corner points defining the structured
topology. Required when the surface has **more than 4
bounding curves** (e.g. after a face split) or when Gmsh
can't auto-detect the corners. Pass ``None`` for a clean
3- or 4-sided face.
Returns
-------
_Structured
``self`` for chaining.
Prerequisites
-------------
Every bounding curve of the surface must already be transfinite
(see :meth:`set_transfinite_curve`). Opposite edges must have
matching ``n_nodes`` (or the mesher will fail at generation time
with a "transfinite surface: inconsistent number of nodes" error).
Examples
--------
Clean rectangle::
m.mesh.structured.set_transfinite_surface(face_tag)
Surface with 5+ bounding curves — pick the 4 logical corners::
m.mesh.structured.set_transfinite_surface(
face_tag, corners=[p1, p2, p3, p4],
)
Apply to every horizontal face of a layer::
faces = m.model.select("layer_1", dim=2).result()
m.mesh.structured.set_transfinite_surface(
faces.normal_along("z").tags(),
)
Notes
-----
For a quad/hex mesh, follow with :meth:`set_recombine` on the
same surface. The all-in-one helper :meth:`set_transfinite_box`
does this automatically for clean hex volumes.
"""
for t in self._resolve(tag, dim=2):
gmsh.model.mesh.setTransfiniteSurface(t, arrangement=arrangement,
cornerTags=corners or [])
self._mesh._directives.append({
'kind': 'transfinite_surface', 'tag': t,
'arrangement': arrangement,
'corners': corners or [],
})
self._mesh._log(
f"set_transfinite_surface(tag={t}, "
f"arrangement={arrangement!r})"
)
return self
|
set_transfinite_volume
set_transfinite_volume(tag, *, corners: list[int] | None = None) -> '_Structured'
Force a volume to be meshed as a structured grid.
A transfinite volume has its interior nodes laid out by transfinite
interpolation between its bounding surfaces. Combined with
:meth:set_recombine on each face, this produces a pure hex mesh.
Parameters
tag :
Volume identifier. Accepts an int tag, label string,
physical-group name, (3, tag) dimtag, or list of any.
corners : list[int] | None, default None
Tags of the 6 (prism) or 8 (hex) corner points that define
the structured topology. Required when the volume has
irregular face counts or when Gmsh can't auto-detect the
corners. Pass None for a clean 5- or 6-faced volume.
Returns
_Structured
self for chaining.
Prerequisites
- Every bounding surface must already be transfinite
(:meth:
set_transfinite_surface).
- Opposite surfaces must have matching node counts on their
shared edges.
Examples
Single hex (after edges and faces are already transfinite)::
m.mesh.structured.set_transfinite_volume(vol_tag)
Notes
For the common case of "transfinite + recombine + hex on every
face of a clean box," use :meth:set_transfinite_box instead —
it sets the constraints on edges, faces, and volume in one call.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite_volume(
self,
tag,
*,
corners: list[int] | None = None,
) -> "_Structured":
"""Force a volume to be meshed as a structured grid.
A transfinite volume has its interior nodes laid out by transfinite
interpolation between its bounding surfaces. Combined with
:meth:`set_recombine` on each face, this produces a pure hex mesh.
Parameters
----------
tag :
Volume identifier. Accepts an int tag, label string,
physical-group name, ``(3, tag)`` dimtag, or list of any.
corners : list[int] | None, default ``None``
Tags of the 6 (prism) or 8 (hex) corner points that define
the structured topology. Required when the volume has
irregular face counts or when Gmsh can't auto-detect the
corners. Pass ``None`` for a clean 5- or 6-faced volume.
Returns
-------
_Structured
``self`` for chaining.
Prerequisites
-------------
- Every bounding surface must already be transfinite
(:meth:`set_transfinite_surface`).
- Opposite surfaces must have matching node counts on their
shared edges.
Examples
--------
Single hex (after edges and faces are already transfinite)::
m.mesh.structured.set_transfinite_volume(vol_tag)
Notes
-----
For the common case of "transfinite + recombine + hex on every
face of a clean box," use :meth:`set_transfinite_box` instead —
it sets the constraints on edges, faces, and volume in one call.
"""
for t in self._resolve(tag, dim=3):
gmsh.model.mesh.setTransfiniteVolume(t, cornerTags=corners or [])
self._mesh._directives.append({
'kind': 'transfinite_volume', 'tag': t,
'corners': corners or [],
})
self._mesh._log(f"set_transfinite_volume(tag={t})")
return self
|
set_transfinite_automatic
set_transfinite_automatic(dim_tags: list[DimTag] | None = None, *, corner_angle: float = 2.35, recombine: bool = True) -> '_Structured'
Auto-detect transfinite-compatible entities and constrain them.
Walks each surface and volume in dim_tags (or the entire model
if None). A face counts as "transfinite-compatible" if it
has 3 or 4 corners whose angles are within corner_angle of a
flat angle (π radians). Compatible faces and the volumes built
from them get transfinite + (optionally) recombine constraints
applied automatically.
Useful as a fallback after boolean operations leave you with a
mix of clean and split faces — :meth:set_transfinite_box
would fail on the split faces, but automatic simply skips
them.
Parameters
dim_tags : list[(dim, tag)] | None, default None
Restrict the search to these entities. None ⇒ walk
every entity in the model.
corner_angle : float, default 2.35
Threshold angle in radians for the "is this a corner?"
test. The default ≈ 135° is Gmsh's own — vertices whose
interior angle deviates from π (180°) by less than this
tolerance are not counted as corners. Reduce for stricter
corner detection; raise to admit more rounded transitions.
recombine : bool, default True
Recombine detected faces into quads (and volumes into hexes).
Set False for a transfinite tet mesh.
Returns
_Structured
self for chaining.
Examples
Mesh-everything-it-can fallback after a boolean op::
m.model.boolean.fragment("box_a", "box_b")
m.mesh.structured.set_transfinite_automatic()
m.mesh.generation.generate(dim=3)
Restrict to a subset (only the volumes you care about)::
m.mesh.structured.set_transfinite_automatic(
dim_tags=[(3, t) for t in vol_tags],
)
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite_automatic(
self,
dim_tags : list[DimTag] | None = None,
*,
corner_angle: float = 2.35,
recombine : bool = True,
) -> "_Structured":
"""Auto-detect transfinite-compatible entities and constrain them.
Walks each surface and volume in ``dim_tags`` (or the entire model
if ``None``). A face counts as "transfinite-compatible" if it
has 3 or 4 corners whose angles are within ``corner_angle`` of a
flat angle (π radians). Compatible faces and the volumes built
from them get transfinite + (optionally) recombine constraints
applied automatically.
Useful as a fallback after boolean operations leave you with a
mix of clean and split faces — :meth:`set_transfinite_box`
would fail on the split faces, but ``automatic`` simply skips
them.
Parameters
----------
dim_tags : list[(dim, tag)] | None, default ``None``
Restrict the search to these entities. ``None`` ⇒ walk
every entity in the model.
corner_angle : float, default ``2.35``
Threshold angle in **radians** for the "is this a corner?"
test. The default ≈ 135° is Gmsh's own — vertices whose
interior angle deviates from π (180°) by less than this
tolerance are not counted as corners. Reduce for stricter
corner detection; raise to admit more rounded transitions.
recombine : bool, default ``True``
Recombine detected faces into quads (and volumes into hexes).
Set ``False`` for a transfinite tet mesh.
Returns
-------
_Structured
``self`` for chaining.
Examples
--------
Mesh-everything-it-can fallback after a boolean op::
m.model.boolean.fragment("box_a", "box_b")
m.mesh.structured.set_transfinite_automatic()
m.mesh.generation.generate(dim=3)
Restrict to a subset (only the volumes you care about)::
m.mesh.structured.set_transfinite_automatic(
dim_tags=[(3, t) for t in vol_tags],
)
"""
gmsh.model.mesh.setTransfiniteAutomatic(
dimTags=dim_tags or [],
cornerAngle=corner_angle,
recombine=recombine,
)
self._mesh._directives.append({
'kind': 'transfinite_automatic',
'dim_tags': dim_tags or [],
'corner_angle': corner_angle,
'recombine': recombine,
})
self._mesh._log(
f"set_transfinite_automatic("
f"corner_angle={math.degrees(corner_angle):.1f}°, "
f"recombine={recombine})"
)
return self
|
set_transfinite_box
set_transfinite_box(vol, *, size: float | None = None, n: int | None = None, recombine: bool = True) -> '_Structured'
Apply transfinite + recombine constraints to a clean hex volume.
Captures the full "structured hex" setup in one call. Walks the
volume's bounding curves, assigns a node count per edge, marks
every bounding surface as transfinite (and recombined to quads
when recombine=True), then marks the volume itself as
transfinite.
Parameters
vol :
Volume identifier. Accepts an int tag, a label string, a
physical-group name, or a (3, tag) dimtag. If it
resolves to multiple volumes, all are constrained.
size : float, optional
Target element edge length. Node count per edge is
round(edge_length / size) + 1 (clamped to a minimum
of 2 nodes). Provides isotropic sizing — each edge gets
its own n_nodes based on its length.
n : int, optional
Uniform node count on every edge of the volume.
Overrides per-edge length-based sizing.
recombine : bool, default True
Recombine each face to quads (gives a hex mesh). Set
False for a transfinite tet mesh.
Returns
_Structured
self for chaining.
Raises
ValueError
If neither size nor n is given, or both are.
Requirements
The volume must be hex-decomposable: exactly 5 or 6 faces,
each a 3- or 4-sided patch. After :meth:boolean.fragment
operations, volumes may end up with split faces and stop being
transfinite-compatible — in that case use
:meth:set_transfinite_automatic instead, which silently
skips incompatible faces.
Examples
Uniform mesh — same n per edge regardless of edge length::
m.mesh.structured.set_transfinite_box("box", n=11)
Length-based sizing — denser mesh on longer edges::
m.mesh.structured.set_transfinite_box("box", size=0.5)
Per-axis control — use the lower-level methods instead::
edges = m.model.select("box", dim=1).result()
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("x").tags(), n_nodes=11)
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("y").tags(), n_nodes=11)
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("z").tags(), n_nodes=21)
# then surfaces + volume via set_transfinite_box(recombine=...)
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite_box(
self,
vol,
*,
size: float | None = None,
n : int | None = None,
recombine: bool = True,
) -> "_Structured":
"""Apply transfinite + recombine constraints to a clean hex volume.
Captures the full "structured hex" setup in one call. Walks the
volume's bounding curves, assigns a node count per edge, marks
every bounding surface as transfinite (and recombined to quads
when ``recombine=True``), then marks the volume itself as
transfinite.
Parameters
----------
vol :
Volume identifier. Accepts an int tag, a label string, a
physical-group name, or a ``(3, tag)`` dimtag. If it
resolves to multiple volumes, all are constrained.
size : float, optional
Target element edge length. Node count per edge is
``round(edge_length / size) + 1`` (clamped to a minimum
of 2 nodes). Provides isotropic sizing — each edge gets
its own ``n_nodes`` based on its length.
n : int, optional
Uniform node count on **every** edge of the volume.
Overrides per-edge length-based sizing.
recombine : bool, default ``True``
Recombine each face to quads (gives a hex mesh). Set
``False`` for a transfinite tet mesh.
Returns
-------
_Structured
``self`` for chaining.
Raises
------
ValueError
If neither ``size`` nor ``n`` is given, or both are.
Requirements
------------
The volume must be **hex-decomposable**: exactly 5 or 6 faces,
each a 3- or 4-sided patch. After :meth:`boolean.fragment`
operations, volumes may end up with split faces and stop being
transfinite-compatible — in that case use
:meth:`set_transfinite_automatic` instead, which silently
skips incompatible faces.
Examples
--------
Uniform mesh — same ``n`` per edge regardless of edge length::
m.mesh.structured.set_transfinite_box("box", n=11)
Length-based sizing — denser mesh on longer edges::
m.mesh.structured.set_transfinite_box("box", size=0.5)
Per-axis control — use the lower-level methods instead::
edges = m.model.select("box", dim=1).result()
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("x").tags(), n_nodes=11)
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("y").tags(), n_nodes=11)
m.mesh.structured.set_transfinite_curve(
edges.parallel_to("z").tags(), n_nodes=21)
# then surfaces + volume via set_transfinite_box(recombine=...)
"""
if (size is None) == (n is None):
raise ValueError("Pass exactly one of size= or n=.")
# Resolve volume → list of dim=3 tags
tags = self._resolve(vol, dim=3)
for vtag in tags:
edges = self._mesh._parent.model.queries.boundary_curves(vtag)
faces = self._mesh._parent.model.queries.boundary(vtag, oriented=False)
for _, ctag in edges:
if n is not None:
n_edge = n
else:
bb = self._mesh._parent.model.queries.bounding_box(ctag, dim=1)
L = max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2])
n_edge = max(2, round(L / size) + 1)
self.set_transfinite_curve(ctag, n_edge)
for _, stag in faces:
self.set_transfinite_surface(stag)
if recombine:
self.set_recombine(stag, dim=2)
self.set_transfinite_volume(vtag)
self._mesh._log(
f"set_transfinite_box(vol={vol!r}, size={size}, n={n}, "
f"recombine={recombine}) — applied to {len(tags)} volume(s)"
)
return self
|
set_transfinite
set_transfinite(target=None, *, n=None, size=None, recombine: bool = True, dim: int | None = None, angle_tol_deg: float = 5.0) -> '_Structured'
Apply transfinite (+ optional recombine) to one entity, many, or the whole model.
High-level dispatcher that infers the dim of the target and
applies the full cascade for that dim — curves of bounding
edges, surfaces, recombine, and (for volumes) the volume itself.
Parameters
target :
What to constrain. Accepts:
- ``None`` → every volume in the model
- ``"name"`` (label or PG name) → all entities under that
name, any dim (each gets the cascade for its dim)
- a Selection — uses its dimtags
- a ``(dim, tag)`` dimtag, or list of them
- a bare int — must be paired with ``dim=``
int | tuple | dict | None
Node-count sizing. Pass exactly one of n or size.
int — uniform on every edge of each entity, any
orientation
dict — per-axis, keyed by "x" / "y" / "z".
Requires axis-aligned entities (raises otherwise — the
error reports the cluster directions so you can switch to
tuple form).
tuple — per principal axis, in the order
(X-aligned, Y-aligned, Z-aligned). Works on any
rotation; closest-global-axis assignment with lex
tie-break. Length must match the entity's principal-axis
count (3 for volumes, 2 for surfaces, 1 for curves).
size : float | tuple | dict | None
Length-based sizing — node count per edge is
round(edge_length / size) + 1. Same grammar as n.
recombine : bool, default True
Recombine to quads on each face and (for volumes) hex on
the volume. Set False for a structured tet mesh.
dim : int | None, default None
Only used when target is a bare int. Ignored otherwise.
angle_tol_deg : float, default 5.0
Tolerance for grouping edges into principal-axis clusters.
Raise if your geometry has nearly-parallel non-orthogonal
edges that you want kept separate.
Returns
_Structured
self for chaining.
Behavior
- Volumes are processed first so their bounding surfaces and
edges aren't reset by a later surface-level call.
- Entities whose edges don't cluster into the expected
principal-axis count (e.g. a face split by a boolean op into
a 5-sided patch) are warned and skipped rather than
failing the whole call.
Examples
Whole-model, uniform sizing::
g.mesh.structured.set_transfinite(n=11)
Axis-aligned box, per-axis sizing::
g.mesh.structured.set_transfinite(
"layer_top", n={"x": 101, "y": 101, "z": 6},
)
Rotated box, per principal axis (tuple)::
g.mesh.structured.set_transfinite("rotated_box", n=(11, 11, 21))
Length-based sizing on a named surface::
g.mesh.structured.set_transfinite("base", size={"x": 100, "y": 100})
For per-edge bias (Progression/Bump/Beta) or explicit corner
lists, fall back to the granular methods —
:meth:set_transfinite_curve, :meth:set_transfinite_surface,
:meth:set_transfinite_volume.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite(
self,
target=None,
*,
n=None,
size=None,
recombine: bool = True,
dim: int | None = None,
angle_tol_deg: float = 5.0,
) -> "_Structured":
"""Apply transfinite (+ optional recombine) to one entity, many, or the whole model.
High-level dispatcher that infers the dim of the target and
applies the full cascade for that dim — curves of bounding
edges, surfaces, recombine, and (for volumes) the volume itself.
Parameters
----------
target :
What to constrain. Accepts:
- ``None`` → every volume in the model
- ``"name"`` (label or PG name) → all entities under that
name, any dim (each gets the cascade for its dim)
- a Selection — uses its dimtags
- a ``(dim, tag)`` dimtag, or list of them
- a bare int — must be paired with ``dim=``
n : int | tuple | dict | None
Node-count sizing. Pass exactly one of ``n`` or ``size``.
- ``int`` — uniform on every edge of each entity, any
orientation
- ``dict`` — per-axis, keyed by ``"x"`` / ``"y"`` / ``"z"``.
Requires axis-aligned entities (raises otherwise — the
error reports the cluster directions so you can switch to
tuple form).
- ``tuple`` — per principal axis, in the order
``(X-aligned, Y-aligned, Z-aligned)``. Works on any
rotation; closest-global-axis assignment with lex
tie-break. Length must match the entity's principal-axis
count (3 for volumes, 2 for surfaces, 1 for curves).
size : float | tuple | dict | None
Length-based sizing — node count per edge is
``round(edge_length / size) + 1``. Same grammar as ``n``.
recombine : bool, default True
Recombine to quads on each face and (for volumes) hex on
the volume. Set ``False`` for a structured tet mesh.
dim : int | None, default None
Only used when ``target`` is a bare int. Ignored otherwise.
angle_tol_deg : float, default 5.0
Tolerance for grouping edges into principal-axis clusters.
Raise if your geometry has nearly-parallel non-orthogonal
edges that you want kept separate.
Returns
-------
_Structured
``self`` for chaining.
Behavior
--------
- Volumes are processed first so their bounding surfaces and
edges aren't reset by a later surface-level call.
- Entities whose edges don't cluster into the expected
principal-axis count (e.g. a face split by a boolean op into
a 5-sided patch) are **warned and skipped** rather than
failing the whole call.
Examples
--------
Whole-model, uniform sizing::
g.mesh.structured.set_transfinite(n=11)
Axis-aligned box, per-axis sizing::
g.mesh.structured.set_transfinite(
"layer_top", n={"x": 101, "y": 101, "z": 6},
)
Rotated box, per principal axis (tuple)::
g.mesh.structured.set_transfinite("rotated_box", n=(11, 11, 21))
Length-based sizing on a named surface::
g.mesh.structured.set_transfinite("base", size={"x": 100, "y": 100})
For per-edge bias (Progression/Bump/Beta) or explicit corner
lists, fall back to the granular methods —
:meth:`set_transfinite_curve`, :meth:`set_transfinite_surface`,
:meth:`set_transfinite_volume`.
"""
from apeGmsh.core._helpers import resolve_to_dimtags
from apeGmsh.core._selection import (
_cluster_edge_directions,
_order_clusters_by_global_axis,
_AXIS_VECTORS,
)
import warnings
if (n is None) == (size is None):
raise ValueError(
"set_transfinite: pass exactly one of n= or size=."
)
spec_kind = "n" if n is not None else "size"
spec = n if n is not None else size
# Resolve target → list of dimtags
if target is None:
dts = [(3, t) for _, t in gmsh.model.getEntities(3)]
else:
dts = resolve_to_dimtags(
target, default_dim=dim or 3, session=self._mesh._parent,
)
# Group by dim — process volumes first
by_dim = {1: [], 2: [], 3: []}
for dt in dts:
if dt[0] in by_dim:
by_dim[dt[0]].append(dt)
for vol_dt in by_dim[3]:
self._cascade_volume(
vol_dt, spec, spec_kind, recombine, angle_tol_deg,
)
# Standalone surfaces (not part of any volume we just processed)
already_handled_faces = set()
for vol_dt in by_dim[3]:
for fdt in self._mesh._parent.model.queries.boundary(
vol_dt, oriented=False,
):
already_handled_faces.add(tuple(fdt))
for face_dt in by_dim[2]:
if tuple(face_dt) in already_handled_faces:
continue
self._cascade_surface(
face_dt, spec, spec_kind, recombine, angle_tol_deg,
)
for curve_dt in by_dim[1]:
# Curves take only a scalar; reject dict/tuple at the curve level
if isinstance(spec, (dict, tuple, list)):
# Pull the matching value
count = self._extract_curve_count(curve_dt, spec, spec_kind,
angle_tol_deg)
else:
count = self._spec_to_curve_count(curve_dt, spec, spec_kind)
self.set_transfinite_curve(curve_dt[1], n_nodes=count)
return self
|
set_transfinite_by_physical
set_transfinite_by_physical(name: str, *, dim: int, **kwargs) -> '_Structured'
Deprecated. set_transfinite_curve/surface/volume already
accept a label or physical-group name directly — pass it as
tag.
Example
::
# old
g.mesh.structured.set_transfinite_by_physical("flange", dim=2,
arrangement="Left")
# new
g.mesh.structured.set_transfinite_surface("flange",
arrangement="Left")
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_transfinite_by_physical(
self,
name : str,
*,
dim : int,
**kwargs,
) -> "_Structured":
"""
Deprecated. ``set_transfinite_curve/surface/volume`` already
accept a label or physical-group name directly — pass it as
``tag``.
Example
-------
::
# old
g.mesh.structured.set_transfinite_by_physical("flange", dim=2,
arrangement="Left")
# new
g.mesh.structured.set_transfinite_surface("flange",
arrangement="Left")
"""
import warnings
warnings.warn(
"set_transfinite_by_physical is deprecated; "
"set_transfinite_curve/surface/volume already accept a "
"physical-group name as tag.",
DeprecationWarning,
stacklevel=2,
)
if dim == 1:
return self.set_transfinite_curve(name, **kwargs)
if dim == 2:
return self.set_transfinite_surface(name, **kwargs)
if dim == 3:
return self.set_transfinite_volume(name, **kwargs)
raise ValueError(
f"set_transfinite_by_physical: dim must be 1, 2, or 3, got {dim!r}"
)
|
set_recombine
set_recombine(tag, *, dim: int = 2, angle: float = 45.0) -> '_Structured'
Request quad recombination. tag accepts int, label, or PG name.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_recombine(
self,
tag,
*,
dim : int = 2,
angle: float = 45.0,
) -> "_Structured":
"""Request quad recombination. ``tag`` accepts int, label, or PG name."""
for t in self._resolve(tag, dim=dim):
gmsh.model.mesh.setRecombine(dim, t, angle)
self._mesh._directives.append({
'kind': 'recombine', 'dim': dim, 'tag': t, 'angle': angle,
})
self._mesh._log(f"set_recombine(dim={dim}, tag={t}, angle={angle}°)")
return self
|
recombine
recombine() -> '_Structured'
Globally recombine all triangular elements into quads.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def recombine(self) -> "_Structured":
"""Globally recombine all triangular elements into quads."""
gmsh.model.mesh.recombine()
self._mesh._log("recombine()")
return self
|
set_recombine_by_physical
set_recombine_by_physical(name: str, *, dim: int = 2, angle: float = 45.0) -> '_Structured'
Deprecated. set_recombine accepts a PG name directly.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_recombine_by_physical(
self,
name : str,
*,
dim : int = 2,
angle: float = 45.0,
) -> "_Structured":
"""Deprecated. ``set_recombine`` accepts a PG name directly."""
import warnings
warnings.warn(
"set_recombine_by_physical is deprecated; pass the "
"physical-group name to set_recombine() as tag.",
DeprecationWarning,
stacklevel=2,
)
return self.set_recombine(name, dim=dim, angle=angle)
|
set_smoothing
set_smoothing(tag, val: int, *, dim: int = 2) -> '_Structured'
Set smoothing passes. tag accepts int, label, or PG name.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_smoothing(self, tag, val: int, *, dim: int = 2) -> "_Structured":
"""Set smoothing passes. ``tag`` accepts int, label, or PG name."""
for t in self._resolve(tag, dim=dim):
gmsh.model.mesh.setSmoothing(dim, t, val)
self._mesh._directives.append({
'kind': 'smoothing', 'dim': dim, 'tag': t, 'val': val,
})
self._mesh._log(f"set_smoothing(dim={dim}, tag={t}, val={val})")
return self
|
set_smoothing_by_physical
set_smoothing_by_physical(name: str, val: int, *, dim: int = 2) -> '_Structured'
Deprecated. set_smoothing accepts a PG name directly.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_smoothing_by_physical(
self,
name: str,
val : int,
*,
dim : int = 2,
) -> "_Structured":
"""Deprecated. ``set_smoothing`` accepts a PG name directly."""
import warnings
warnings.warn(
"set_smoothing_by_physical is deprecated; pass the "
"physical-group name to set_smoothing() as tag.",
DeprecationWarning,
stacklevel=2,
)
return self.set_smoothing(name, val, dim=dim)
|
set_compound
set_compound(dim: int, tags) -> '_Structured'
Merge entities so they are meshed together as a single compound.
tags accepts int, label/PG name, (dim, tag) tuple, or a
list of any mix.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def set_compound(self, dim: int, tags) -> "_Structured":
"""Merge entities so they are meshed together as a single compound.
``tags`` accepts int, label/PG name, ``(dim, tag)`` tuple, or a
list of any mix.
"""
resolved = self._resolve(tags, dim=dim)
gmsh.model.mesh.setCompound(dim, resolved)
self._mesh._log(f"set_compound(dim={dim}, tags={resolved})")
return self
|
remove_constraints
remove_constraints(dim_tags=None) -> '_Structured'
Remove all meshing constraints from the given (or all) entities.
dim_tags accepts any flexible-ref form (int, label/PG name,
(dim, tag), or list thereof). None clears every
entity in the model.
Source code in src/apeGmsh/mesh/_mesh_structured.py
| def remove_constraints(self, dim_tags=None) -> "_Structured":
"""Remove all meshing constraints from the given (or all) entities.
``dim_tags`` accepts any flexible-ref form (int, label/PG name,
``(dim, tag)``, or list thereof). ``None`` clears every
entity in the model.
"""
if dim_tags is None:
dts: list[DimTag] = []
else:
from apeGmsh.core._helpers import resolve_to_dimtags
dts = resolve_to_dimtags(
dim_tags, default_dim=3, session=self._mesh._parent,
)
gmsh.model.mesh.removeConstraints(dimTags=dts)
self._mesh._log(f"remove_constraints(dim_tags={dim_tags})")
return self
|
One-call recipe — set_transfinite()
For the common case of "apply transfinite + recombine to one entity, many,
or the whole model," use the unified
set_transfinite()
method. It infers the dim from the target and runs the appropriate cascade
(curves → faces → recombine → volume):
# Whole model, uniform — typical "just give me hexes"
g.mesh.structured.set_transfinite(n=11)
# Axis-aligned hex, per-axis sizing (most readable)
g.mesh.structured.set_transfinite("layer_top",
n={"x": 101, "y": 101, "z": 6})
# Rotated hex, per principal axis (works for any orientation)
g.mesh.structured.set_transfinite("rotated_box", n=(11, 11, 21))
# Length-based sizing (per axis)
g.mesh.structured.set_transfinite("layer_top",
size={"x": 100, "y": 100, "z": 10})
Sizing forms — all three coexist; pick by readability:
| Form |
Use when |
Example |
scalar n=11 |
uniform on every edge; any orientation |
n=11 |
dict n={"x":..., "y":..., "z":...} |
axis-aligned hex, want per-axis counts |
n={"x":11, "y":11, "z":21} |
tuple n=(n1, n2, n3) |
any rotation; counts in (X-closest, Y-closest, Z-closest) order |
n=(11, 11, 21) |
Behavior on incompatible geometry — entities whose edges don't cluster
into the expected principal-axis count (e.g. a face split by a boolean op
into a 5-sided patch) are warned and skipped rather than failing the
whole call. Use
set_transfinite_automatic()
if you want silent skipping with no warning.
For per-edge bias (Progression / Bump / Beta with coef=), explicit
corner lists, or custom triangle arrangement, drop to the granular methods:
set_transfinite_curve(), set_transfinite_surface(),
set_transfinite_volume().
g.mesh.editing
apeGmsh.mesh._mesh_editing._Editing
_Editing(parent_mesh: 'Mesh')
Mesh mutation, embedding, periodicity, STL import.
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
embed
embed(tags, in_tag, *, dim: int = 0, in_dim: int = 3) -> '_Editing'
Embed lower-dimensional entities inside a higher-dimensional
entity so the mesh is conforming along them.
Parameters accept int tags, label/PG strings, or lists thereof.
Example
::
g.mesh.editing.embed("crack_surf", "body", dim=2, in_dim=3)
g.mesh.editing.embed([p1, p2, p3], surf_tag, dim=0, in_dim=2)
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def embed(
self,
tags,
in_tag,
*,
dim : int = 0,
in_dim: int = 3,
) -> "_Editing":
"""
Embed lower-dimensional entities inside a higher-dimensional
entity so the mesh is conforming along them.
Parameters accept int tags, label/PG strings, or lists thereof.
Example
-------
::
g.mesh.editing.embed("crack_surf", "body", dim=2, in_dim=3)
g.mesh.editing.embed([p1, p2, p3], surf_tag, dim=0, in_dim=2)
"""
from apeGmsh.core._helpers import resolve_to_tags
tag_list = resolve_to_tags(tags, dim=dim, session=self._mesh._parent)
in_tags = resolve_to_tags(in_tag, dim=in_dim, session=self._mesh._parent)
in_tag_resolved = in_tags[0]
gmsh.model.mesh.embed(dim, tag_list, in_dim, in_tag_resolved)
self._mesh._log(
f"embed(dim={dim}, tags={tag_list}, "
f"in_dim={in_dim}, in_tag={in_tag})"
)
return self
|
set_periodic
set_periodic(tags, master_tags, transform: list[float], *, dim: int = 2) -> '_Editing'
Declare periodic mesh correspondence between entities.
Parameters
tags : slave entity reference(s) — int, label, PG name,
(dim, tag) tuple, or list of any mix.
master_tags : master entity reference(s) — same flexible form.
transform : 16-element row-major 4×4 affine matrix mapping
master -> slave coordinates
dim : entity dimension (1 = curves, 2 = surfaces)
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def set_periodic(
self,
tags,
master_tags,
transform : list[float],
*,
dim : int = 2,
) -> "_Editing":
"""
Declare periodic mesh correspondence between entities.
Parameters
----------
tags : slave entity reference(s) — int, label, PG name,
``(dim, tag)`` tuple, or list of any mix.
master_tags : master entity reference(s) — same flexible form.
transform : 16-element row-major 4×4 affine matrix mapping
master -> slave coordinates
dim : entity dimension (1 = curves, 2 = surfaces)
"""
from apeGmsh.core._helpers import resolve_to_tags
slave_resolved = resolve_to_tags(
tags, dim=dim, session=self._mesh._parent,
)
master_resolved = resolve_to_tags(
master_tags, dim=dim, session=self._mesh._parent,
)
if len(slave_resolved) != len(master_resolved):
raise ValueError(
f"set_periodic: slave/master count mismatch — "
f"slaves={slave_resolved} ({len(slave_resolved)}), "
f"masters={master_resolved} ({len(master_resolved)}). "
f"Each slave needs exactly one master under the same "
f"transform."
)
gmsh.model.mesh.setPeriodic(
dim, slave_resolved, master_resolved, transform,
)
self._mesh._log(
f"set_periodic(dim={dim}, tags={slave_resolved}, "
f"master={master_resolved})"
)
return self
|
import_stl
import_stl() -> '_Editing'
Classify an STL mesh previously loaded into the gmsh model via
gmsh.merge as a discrete surface mesh.
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def import_stl(self) -> "_Editing":
"""
Classify an STL mesh previously loaded into the gmsh model via
``gmsh.merge`` as a discrete surface mesh.
"""
gmsh.model.mesh.importStl()
self._mesh._log("import_stl()")
return self
|
classify_surfaces
classify_surfaces(angle: float, *, boundary: bool = True, for_reparametrization: bool = False, curve_angle: float = math.pi, export_discrete: bool = True) -> '_Editing'
Partition a discrete STL mesh into surface patches based on
dihedral angle.
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def classify_surfaces(
self,
angle : float,
*,
boundary : bool = True,
for_reparametrization: bool = False,
curve_angle : float = math.pi,
export_discrete : bool = True,
) -> "_Editing":
"""
Partition a discrete STL mesh into surface patches based on
dihedral angle.
"""
gmsh.model.mesh.classifySurfaces(
angle,
boundary=boundary,
forReparametrization=for_reparametrization,
curveAngle=curve_angle,
exportDiscrete=export_discrete,
)
self._mesh._log(
f"classify_surfaces(angle={math.degrees(angle):.1f}°, "
f"boundary={boundary})"
)
return self
|
create_geometry
create_geometry(dim_tags: list[DimTag] | None = None) -> '_Editing'
Create a proper CAD-like geometry from classified discrete surfaces.
Must be called after classify_surfaces.
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def create_geometry(
self,
dim_tags: list[DimTag] | None = None,
) -> "_Editing":
"""
Create a proper CAD-like geometry from classified discrete surfaces.
Must be called after ``classify_surfaces``.
"""
gmsh.model.mesh.createGeometry(dimTags=dim_tags or [])
self._mesh._log("create_geometry()")
return self
|
clear
clear(dim_tags=None) -> '_Editing'
Clear mesh data (nodes + elements).
dim_tags accepts any flexible-ref form — int, label/PG name,
(dim, tag), or a list mixing those — resolved via
:func:resolve_to_dimtags (default_dim=3). None (the
default) clears every entity in the model.
Example
::
g.mesh.editing.clear() # clear everything
g.mesh.editing.clear("col.body") # clear a labelled volume
g.mesh.editing.clear([(2, 5), "fillet"]) # mixed refs
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def clear(self, dim_tags=None) -> "_Editing":
"""Clear mesh data (nodes + elements).
``dim_tags`` accepts any flexible-ref form — int, label/PG name,
``(dim, tag)``, or a list mixing those — resolved via
:func:`resolve_to_dimtags` (default_dim=3). ``None`` (the
default) clears every entity in the model.
Example
-------
::
g.mesh.editing.clear() # clear everything
g.mesh.editing.clear("col.body") # clear a labelled volume
g.mesh.editing.clear([(2, 5), "fillet"]) # mixed refs
"""
if dim_tags is None:
dts: list[DimTag] = []
else:
from apeGmsh.core._helpers import resolve_to_dimtags
dts = resolve_to_dimtags(
dim_tags, default_dim=3, session=self._mesh._parent,
)
gmsh.model.mesh.clear(dimTags=dts)
self._mesh._log(f"clear(dim_tags={dim_tags})")
return self
|
reverse
reverse(dim_tags=None) -> '_Editing'
Reverse the orientation of mesh elements in the given entities.
dim_tags accepts any flexible-ref form (int, label/PG name,
(dim, tag), or list thereof). None reverses every
entity in the model.
Example
::
g.mesh.editing.reverse("inverted_face")
g.mesh.editing.reverse([(2, 5), (2, 6)])
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def reverse(self, dim_tags=None) -> "_Editing":
"""Reverse the orientation of mesh elements in the given entities.
``dim_tags`` accepts any flexible-ref form (int, label/PG name,
``(dim, tag)``, or list thereof). ``None`` reverses every
entity in the model.
Example
-------
::
g.mesh.editing.reverse("inverted_face")
g.mesh.editing.reverse([(2, 5), (2, 6)])
"""
if dim_tags is None:
dts: list[DimTag] = []
else:
from apeGmsh.core._helpers import resolve_to_dimtags
dts = resolve_to_dimtags(
dim_tags, default_dim=3, session=self._mesh._parent,
)
gmsh.model.mesh.reverse(dimTags=dts)
self._mesh._log(f"reverse(dim_tags={dim_tags})")
return self
|
relocate_nodes
relocate_nodes(*, dim: int = -1, tag=-1) -> '_Editing'
Project mesh nodes back onto their underlying geometry.
tag accepts an int, label/PG name, (dim, tag), or a
list mixing those. Because gmsh's relocateNodes operates
on a single entity at a time, when a reference resolves to
multiple entities the wrapper iterates and calls gmsh once
per resolved (dim, tag).
tag=-1 (the default) relocates nodes for every entity in
the model; dim is forwarded to gmsh in that case.
Example
::
g.mesh.editing.relocate_nodes() # all entities
g.mesh.editing.relocate_nodes(tag="col.faces") # whole label
g.mesh.editing.relocate_nodes(tag=(2, 5))
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def relocate_nodes(self, *, dim: int = -1, tag=-1) -> "_Editing":
"""Project mesh nodes back onto their underlying geometry.
``tag`` accepts an int, label/PG name, ``(dim, tag)``, or a
list mixing those. Because gmsh's ``relocateNodes`` operates
on a single entity at a time, when a reference resolves to
multiple entities the wrapper iterates and calls gmsh once
per resolved ``(dim, tag)``.
``tag=-1`` (the default) relocates nodes for every entity in
the model; ``dim`` is forwarded to gmsh in that case.
Example
-------
::
g.mesh.editing.relocate_nodes() # all entities
g.mesh.editing.relocate_nodes(tag="col.faces") # whole label
g.mesh.editing.relocate_nodes(tag=(2, 5))
"""
if tag == -1:
gmsh.model.mesh.relocateNodes(dim=dim, tag=-1)
self._mesh._log(f"relocate_nodes(dim={dim}, tag=-1)")
return self
from apeGmsh.core._helpers import resolve_to_dimtags
default_dim = dim if dim != -1 else 3
dts = resolve_to_dimtags(
tag, default_dim=default_dim, session=self._mesh._parent,
)
for d, t in dts:
gmsh.model.mesh.relocateNodes(dim=d, tag=t)
self._mesh._log(f"relocate_nodes(resolved={dts})")
return self
|
remove_duplicate_nodes
remove_duplicate_nodes(verbose: bool = True) -> '_Editing'
Merge nodes that share the same position within tolerance.
Parameters
verbose : if True (default), print how many nodes were merged.
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def remove_duplicate_nodes(self, verbose: bool = True) -> "_Editing":
"""
Merge nodes that share the same position within tolerance.
Parameters
----------
verbose : if True (default), print how many nodes were merged.
"""
before = len(gmsh.model.mesh.getNodes()[0])
gmsh.model.mesh.removeDuplicateNodes()
after = len(gmsh.model.mesh.getNodes()[0])
removed = before - after
if verbose:
if removed > 0:
print(f"remove_duplicate_nodes: merged {removed} "
f"node(s) ({before} -> {after})")
else:
print(f"remove_duplicate_nodes: no duplicates found "
f"({before} nodes unchanged)")
self._mesh._log(f"remove_duplicate_nodes() removed={removed}")
return self
|
remove_duplicate_elements
remove_duplicate_elements(verbose: bool = True) -> '_Editing'
Remove elements with identical node connectivity.
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def remove_duplicate_elements(self, verbose: bool = True) -> "_Editing":
"""Remove elements with identical node connectivity."""
def _count() -> int:
_, tags, _ = gmsh.model.mesh.getElements()
return sum(len(t) for t in tags)
before = _count()
gmsh.model.mesh.removeDuplicateElements()
after = _count()
removed = before - after
if verbose:
if removed > 0:
print(f"remove_duplicate_elements: removed {removed} "
f"element(s) ({before} -> {after})")
else:
print(f"remove_duplicate_elements: no duplicates found "
f"({before} elements unchanged)")
self._mesh._log(f"remove_duplicate_elements() removed={removed}")
return self
|
crack
crack(physical_group: str, *, dim: int = 1, open_boundary: str | None = None, normal: tuple[float, float, float] | None = None, side_labels: tuple[str, str] | bool = True) -> '_Editing'
Duplicate mesh nodes along a physical group to create a crack.
Wraps Gmsh's built-in Crack plugin
(gmsh.plugin.run("Crack")). After meshing, the plugin
walks the elements on one side of physical_group and
reconnects them to a freshly duplicated set of nodes — the
crack is therefore a discontinuity in the mesh, not in the
geometry.
By default the plugin keeps the boundary vertices of the
crack curve shared (e.g. the crack tip in fracture
mechanics). Naming a sub-region of those boundary vertices
in open_boundary overrides the default and duplicates
them too — that is how you model a crack mouth that opens
onto a free surface.
Must be called after g.mesh.generation.generate(...)
— the plugin operates on the mesh, not the geometry.
Parameters
physical_group : str
Name of the physical group containing the crack
curves (dim=1) or surfaces (dim=2). Create it
ahead of time via g.physical.add_curve(..., name=...)
or g.physical.add_surface(..., name=...).
dim : int
Dimension of the crack itself. 1 for a 1-D crack
in a 2-D mesh; 2 for a 2-D crack in a 3-D mesh.
open_boundary : str, optional
Name of a physical group, one dimension lower than
dim, naming the crack-curve boundary vertices that
should also be duplicated. Use this for the crack
mouth (where the crack reaches a free surface). Leave
None for an interior crack so every boundary vertex
(including the tip) stays shared.
normal : (nx, ny, nz), optional
Hint vector forwarded to the plugin to disambiguate the
two sides of the crack when topology alone is not enough.
Almost never needed for clean transfinite or unstructured
meshes.
side_labels : tuple[str, str] or bool, default True
Post-plugin, the crack is owned by two distinct face
entities (the original entity plus a new one created by the
plugin to host the duplicated side). This argument
controls whether they get named physical groups attached:
* ``True`` (default) — auto-derive
``f"{physical_group}_normal"`` and
``f"{physical_group}_inverted"`` and add them as
physical groups, one per face entity.
* ``(normal_name, inverted_name)`` tuple — use these
explicit names instead.
* ``False`` — skip side labeling (legacy behaviour).
**Convention.** ``<pg>_normal`` is the face entity whose
adjacent volume elements sit on the side the *original*
surface normal points toward; ``<pg>_inverted`` is the
face on the opposite side. This is computed at runtime
from the signed distance between an adjacent tet's
centroid and the crack plane along that normal — it does
not assume the plugin's "original vs new" mapping is
stable. Only supported for ``dim=2`` cracks in 3D meshes.
Returns
_Editing (self, for chaining)
Example
Edge crack reaching the bottom edge — duplicate the mouth,
keep the tip shared::
g.physical.add_curve([crack_curve], name="Crack")
g.physical.add_point([base_point], name="CrackBase")
g.mesh.generation.generate(dim=2)
g.mesh.editing.crack(
"Crack", dim=1, open_boundary="CrackBase",
)
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def crack(
self,
physical_group: str,
*,
dim : int = 1,
open_boundary: str | None = None,
normal : tuple[float, float, float] | None = None,
side_labels : tuple[str, str] | bool = True,
) -> "_Editing":
"""
Duplicate mesh nodes along a physical group to create a crack.
Wraps Gmsh's built-in ``Crack`` plugin
(``gmsh.plugin.run("Crack")``). After meshing, the plugin
walks the elements on one side of ``physical_group`` and
reconnects them to a freshly duplicated set of nodes — the
crack is therefore a discontinuity in the mesh, not in the
geometry.
By default the plugin keeps the **boundary vertices of the
crack curve shared** (e.g. the crack tip in fracture
mechanics). Naming a sub-region of those boundary vertices
in ``open_boundary`` overrides the default and **duplicates
them too** — that is how you model a crack mouth that opens
onto a free surface.
Must be called **after** ``g.mesh.generation.generate(...)``
— the plugin operates on the mesh, not the geometry.
Parameters
----------
physical_group : str
Name of the physical group containing the crack
curves (``dim=1``) or surfaces (``dim=2``). Create it
ahead of time via ``g.physical.add_curve(..., name=...)``
or ``g.physical.add_surface(..., name=...)``.
dim : int
Dimension of the crack itself. ``1`` for a 1-D crack
in a 2-D mesh; ``2`` for a 2-D crack in a 3-D mesh.
open_boundary : str, optional
Name of a physical group, **one dimension lower than**
``dim``, naming the crack-curve boundary vertices that
should *also* be duplicated. Use this for the crack
mouth (where the crack reaches a free surface). Leave
``None`` for an interior crack so every boundary vertex
(including the tip) stays shared.
normal : (nx, ny, nz), optional
Hint vector forwarded to the plugin to disambiguate the
two sides of the crack when topology alone is not enough.
Almost never needed for clean transfinite or unstructured
meshes.
side_labels : tuple[str, str] or bool, default True
Post-plugin, the crack is owned by **two** distinct face
entities (the original entity plus a new one created by the
plugin to host the duplicated side). This argument
controls whether they get named physical groups attached:
* ``True`` (default) — auto-derive
``f"{physical_group}_normal"`` and
``f"{physical_group}_inverted"`` and add them as
physical groups, one per face entity.
* ``(normal_name, inverted_name)`` tuple — use these
explicit names instead.
* ``False`` — skip side labeling (legacy behaviour).
**Convention.** ``<pg>_normal`` is the face entity whose
adjacent volume elements sit on the side the *original*
surface normal points toward; ``<pg>_inverted`` is the
face on the opposite side. This is computed at runtime
from the signed distance between an adjacent tet's
centroid and the crack plane along that normal — it does
not assume the plugin's "original vs new" mapping is
stable. Only supported for ``dim=2`` cracks in 3D meshes.
Returns
-------
_Editing (self, for chaining)
Example
-------
Edge crack reaching the bottom edge — duplicate the mouth,
keep the tip shared::
g.physical.add_curve([crack_curve], name="Crack")
g.physical.add_point([base_point], name="CrackBase")
g.mesh.generation.generate(dim=2)
g.mesh.editing.crack(
"Crack", dim=1, open_boundary="CrackBase",
)
"""
physical = getattr(self._mesh._parent, 'physical', None)
if physical is None:
raise RuntimeError(
"crack: session has no 'physical' composite — "
"physical groups are required to invoke the Crack plugin."
)
crack_pg_tag = physical.get_tag(dim, physical_group)
if crack_pg_tag is None:
raise KeyError(
f"crack: no physical group named {physical_group!r} at "
f"dim={dim}. Create it first via "
f"g.physical.add_curve(..., name={physical_group!r}) "
f"(or add_surface for dim=2)."
)
open_pg_tag = 0 # plugin default — no open boundary
if open_boundary is not None:
open_dim = dim - 1
open_pg_tag = physical.get_tag(open_dim, open_boundary)
if open_pg_tag is None:
raise KeyError(
f"crack: no physical group named {open_boundary!r} "
f"at dim={open_dim}. The open boundary lives one "
f"dimension lower than the crack itself."
)
# Snapshot pre-plugin state for side-labeling. We capture
# the source surface's analytic OCC normal *before* the plugin
# runs because the plugin produces a discrete (mesh-only)
# surface for the duplicated side that has no parameterisation
# — only the original entity is OCC-backed and queryable via
# gmsh.model.getNormal.
do_side_labels = (
side_labels is not False and dim == 2
)
pre_ents: set[int] = set()
src_ents: list[int] = []
ref_origin: np.ndarray | None = None
ref_normal: np.ndarray | None = None
if do_side_labels:
pre_ents = {
int(t) for d_, t in gmsh.model.getEntities(dim) if d_ == dim
}
src_ents = sorted(
int(e) for e in
gmsh.model.getEntitiesForPhysicalGroup(dim, crack_pg_tag)
)
if len(src_ents) < 1:
raise RuntimeError(
f"crack: source PG {physical_group!r} resolves to "
f"no entities; side_labels= cannot be applied."
)
src_tag = src_ents[0]
nrm = gmsh.model.getNormal(src_tag, [0.5, 0.5])
n_arr = np.asarray(nrm, dtype=float)
if float(np.linalg.norm(n_arr)) < 1e-12:
raise RuntimeError(
f"crack: source surface {src_tag} returned a "
f"degenerate analytic normal — pass "
f"side_labels=False or a normal= hint."
)
ref_origin = np.asarray(
gmsh.model.occ.getCenterOfMass(2, src_tag), dtype=float,
)
ref_normal = n_arr / float(np.linalg.norm(n_arr))
gmsh.plugin.setNumber("Crack", "Dimension", float(dim))
gmsh.plugin.setNumber("Crack", "PhysicalGroup", float(crack_pg_tag))
gmsh.plugin.setNumber(
"Crack", "OpenBoundaryPhysicalGroup", float(open_pg_tag),
)
if normal is not None:
nx, ny, nz = normal
gmsh.plugin.setNumber("Crack", "NormalX", float(nx))
gmsh.plugin.setNumber("Crack", "NormalY", float(ny))
gmsh.plugin.setNumber("Crack", "NormalZ", float(nz))
gmsh.plugin.run("Crack")
if do_side_labels:
post_ents = {
int(t) for d_, t in gmsh.model.getEntities(dim) if d_ == dim
}
new_ents = sorted(post_ents - pre_ents)
if len(new_ents) != 1:
raise RuntimeError(
f"crack: expected exactly 1 new face entity, got "
f"{len(new_ents)} (src_ents={src_ents}). "
f"side_labels= cannot be applied unambiguously; pass "
f"side_labels=False to skip auto-labeling."
)
normal_name, inverted_name = (
(f"{physical_group}_normal",
f"{physical_group}_inverted")
if side_labels is True
else side_labels
)
new_tag = new_ents[0]
orig_tag = src_ents[0]
# The plugin reconnects exactly one side; the original and
# new entities are guaranteed to lie on opposite sides, so
# we only need to probe one of them. ref_origin/ref_normal
# are the source surface's analytic plane.
assert ref_origin is not None and ref_normal is not None
new_side = self._classify_face_side(
new_tag, ref_origin, ref_normal,
)
normal_tag = new_tag if new_side > 0 else orig_tag
inverted_tag = orig_tag if new_side > 0 else new_tag
physical.add_surface([normal_tag], name=normal_name)
physical.add_surface([inverted_tag], name=inverted_name)
self._mesh._log(
f"crack: side labels {normal_name!r}->entity {normal_tag}, "
f"{inverted_name!r}->entity {inverted_tag}"
)
self._mesh._log(
f"crack(physical_group={physical_group!r}, dim={dim}, "
f"open_boundary={open_boundary!r}) "
f"-> crack_pg_tag={crack_pg_tag}, "
f"open_pg_tag={open_pg_tag}"
)
return self
|
affine_transform(matrix: list[float], dim_tags=None) -> '_Editing'
Apply an affine transformation to mesh nodes (12 coefficients,
row-major 4x3 matrix — translation in last column).
dim_tags accepts any flexible-ref form (int, label/PG name,
(dim, tag), or list thereof). None transforms every
entity in the model.
::
identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]
g.mesh.editing.affine_transform(identity, "col.body")
Source code in src/apeGmsh/mesh/_mesh_editing.py
| def affine_transform(
self,
matrix : list[float],
dim_tags=None,
) -> "_Editing":
"""
Apply an affine transformation to mesh nodes (12 coefficients,
row-major 4x3 matrix — translation in last column).
``dim_tags`` accepts any flexible-ref form (int, label/PG name,
``(dim, tag)``, or list thereof). ``None`` transforms every
entity in the model.
Example
-------
::
identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]
g.mesh.editing.affine_transform(identity, "col.body")
"""
if dim_tags is None:
dts: list[DimTag] = []
else:
from apeGmsh.core._helpers import resolve_to_dimtags
dts = resolve_to_dimtags(
dim_tags, default_dim=3, session=self._mesh._parent,
)
gmsh.model.mesh.affineTransform(matrix, dimTags=dts)
self._mesh._log(f"affine_transform(dim_tags={dim_tags})")
return self
|
g.mesh.queries
apeGmsh.mesh._mesh_queries._Queries
_Queries(parent_mesh: 'Mesh')
Read-only mesh data extraction and quality reporting.
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
get_nodes
get_nodes(*, dim: int = -1, tag: int = -1, include_boundary: bool = False, return_parametric: bool = False) -> dict
Query mesh nodes.
Returns
dict
'tags' : ndarray(N,) — node tags
'coords' : ndarray(N, 3) — XYZ coordinates
'parametric_coords' : ndarray — only if requested
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def get_nodes(
self,
*,
dim : int = -1,
tag : int = -1,
include_boundary : bool = False,
return_parametric: bool = False,
) -> dict:
"""
Query mesh nodes.
Returns
-------
dict
``'tags'`` : ndarray(N,) — node tags
``'coords'`` : ndarray(N, 3) — XYZ coordinates
``'parametric_coords'`` : ndarray — only if requested
"""
node_tags, coords, param = gmsh.model.mesh.getNodes(
dim=dim, tag=tag,
includeBoundary=include_boundary,
returnParametricCoord=return_parametric,
)
result: dict = {
'tags' : np.array(node_tags, dtype=np.int64),
'coords': np.array(coords).reshape(-1, 3),
}
if return_parametric and len(param):
result['parametric_coords'] = np.array(param)
self._mesh._log(f"get_nodes -> {len(node_tags)} nodes")
return result
|
get_elements
get_elements(*, dim: int = -1, tag: int = -1) -> dict
Query mesh elements.
Returns
dict
'types' : list[int] — gmsh element type codes
'tags' : list[ndarray] — element tags per type
'node_tags' : list[ndarray] — connectivity per type
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def get_elements(
self,
*,
dim: int = -1,
tag: int = -1,
) -> dict:
"""
Query mesh elements.
Returns
-------
dict
``'types'`` : list[int] — gmsh element type codes
``'tags'`` : list[ndarray] — element tags per type
``'node_tags'`` : list[ndarray] — connectivity per type
"""
elem_types, elem_tags, node_tags = gmsh.model.mesh.getElements(
dim=dim, tag=tag
)
result = {
'types' : list(elem_types),
'tags' : [np.array(t, dtype=np.int64) for t in elem_tags],
'node_tags': [np.array(n, dtype=np.int64) for n in node_tags],
}
total = sum(len(t) for t in result['tags'])
self._mesh._log(
f"get_elements -> {total} elements "
f"({len(elem_types)} types)"
)
return result
|
get_element_properties
get_element_properties(element_type: int) -> dict
Return metadata for a given gmsh element type code.
Returns
dict
'name', 'dim', 'order', 'n_nodes',
'n_primary_nodes', 'local_coords'.
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def get_element_properties(self, element_type: int) -> dict:
"""
Return metadata for a given gmsh element type code.
Returns
-------
dict
``'name'``, ``'dim'``, ``'order'``, ``'n_nodes'``,
``'n_primary_nodes'``, ``'local_coords'``.
"""
name, dim, order, n_nodes, local_coords, n_primary = \
gmsh.model.mesh.getElementProperties(element_type)
d = max(dim, 1)
return {
'name' : name,
'dim' : dim,
'order' : order,
'n_nodes' : n_nodes,
'n_primary_nodes': n_primary,
'local_coords' : np.array(local_coords).reshape(-1, d),
}
|
get_fem_data
get_fem_data(dim: int | None = None, *, remove_orphans: bool = False) -> FEMData
Extract solver-ready FEM data as a :class:FEMData object.
Must be called after generate().
Parameters
dim : int or None
Element dimension to extract. None extracts all
dimensions present in the mesh.
remove_orphans : bool
If True, remove mesh nodes not connected to any element.
Nodes referenced by constraints, loads, or masses are
always kept. Default False.
Example
::
fem = g.mesh.queries.get_fem_data() # all dims
fem = g.mesh.queries.get_fem_data(dim=3) # 3D only
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def get_fem_data(
self,
dim: int | None = None,
*,
remove_orphans: bool = False,
) -> FEMData:
"""Extract solver-ready FEM data as a :class:`FEMData` object.
Must be called **after** ``generate()``.
Parameters
----------
dim : int or None
Element dimension to extract. ``None`` extracts all
dimensions present in the mesh.
remove_orphans : bool
If True, remove mesh nodes not connected to any element.
Nodes referenced by constraints, loads, or masses are
always kept. Default False.
Example
-------
::
fem = g.mesh.queries.get_fem_data() # all dims
fem = g.mesh.queries.get_fem_data(dim=3) # 3D only
"""
parent = self._mesh._parent
result = FEMData.from_gmsh(
dim=dim, session=parent, remove_orphans=remove_orphans)
self._mesh._log(
f"get_fem_data(dim={dim}) -> "
f"{result.info.n_nodes} nodes, "
f"{result.info.n_elems} elements, "
f"bw={result.info.bandwidth}"
)
return result
|
get_element_qualities
get_element_qualities(element_tags: list[int] | ndarray, quality_name: str = 'minSICN') -> ndarray
Compute quality metrics for the given elements.
Parameters
element_tags : element tags to evaluate
quality_name : "minSICN", "minSIGE", "gamma", or
"minSJ"
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def get_element_qualities(
self,
element_tags: list[int] | ndarray,
quality_name: str = "minSICN",
) -> ndarray:
"""
Compute quality metrics for the given elements.
Parameters
----------
element_tags : element tags to evaluate
quality_name : ``"minSICN"``, ``"minSIGE"``, ``"gamma"``, or
``"minSJ"``
"""
tags = list(element_tags) if not isinstance(element_tags, list) else element_tags
q = gmsh.model.mesh.getElementQualities(tags, qualityName=quality_name)
return np.asarray(q)
|
quality_report
quality_report(*, dim: int = -1, metrics: list[str] | None = None) -> 'pd.DataFrame'
Compute a summary quality report for all mesh elements.
For each element type and quality metric, reports count, min,
max, mean, std, and the percentage of elements below common
thresholds.
Must be called after generate().
Example
::
g.mesh.generation.generate(2)
print(g.mesh.queries.quality_report().to_string())
Source code in src/apeGmsh/mesh/_mesh_queries.py
| def quality_report(
self,
*,
dim: int = -1,
metrics: list[str] | None = None,
) -> "pd.DataFrame":
"""
Compute a summary quality report for all mesh elements.
For each element type and quality metric, reports count, min,
max, mean, std, and the percentage of elements below common
thresholds.
Must be called **after** ``generate()``.
Example
-------
::
g.mesh.generation.generate(2)
print(g.mesh.queries.quality_report().to_string())
"""
import pandas as pd
if metrics is None:
metrics = ["minSICN", "minSIGE", "gamma", "minSJ"]
elems = self.get_elements(dim=dim)
rows: list[dict] = []
for etype, etags in zip(elems['types'], elems['tags']):
if len(etags) == 0:
continue
props = self.get_element_properties(etype)
etype_name = props.get('name', str(etype))
for metric in metrics:
try:
q = gmsh.model.mesh.getElementQualities(
list(etags.astype(int)), qualityName=metric,
)
q = np.asarray(q)
except Exception:
continue # metric not supported for this element type
if len(q) == 0:
continue
row: dict = {
'element_type' : etype_name,
'gmsh_code' : int(etype),
'metric' : metric,
'count' : len(q),
'min' : float(q.min()),
'max' : float(q.max()),
'mean' : float(q.mean()),
'std' : float(q.std()),
'pct_below_0.1': float((q < 0.1).sum() / len(q) * 100),
'pct_below_0.3': float((q < 0.3).sum() / len(q) * 100),
}
rows.append(row)
df = pd.DataFrame(rows)
if not df.empty:
df = df.set_index(['element_type', 'metric']).sort_index()
self._mesh._log(
f"quality_report(dim={dim}) -> "
f"{len(rows)} metric rows across "
f"{df.index.get_level_values('element_type').nunique() if not df.empty else 0} "
f"element types"
)
if self._mesh._parent._verbose and not df.empty:
print("\n--- Mesh Quality Report ---")
print(df.to_string())
return df
|
g.mesh.partitioning
apeGmsh.mesh._mesh_partitioning._Partitioning
_Partitioning(parent_mesh: 'Mesh')
Mesh partitioning plus node / element renumbering.
Accessed via g.mesh.partitioning.
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def __init__(self, parent_mesh: "Mesh") -> None:
self._mesh = parent_mesh
|
renumber
renumber(dim: int = 2, *, method: str = 'rcm', base: int = 1) -> RenumberResult
Renumber nodes and elements in the Gmsh model.
After this call every Gmsh query returns solver-ready contiguous
IDs. Call once, before extracting FEM data with
:meth:~_Queries.get_fem_data.
Parameters
dim : int
Element dimension used to compute bandwidth and to collect
element tags for renumbering.
method : "simple" | "rcm" | "hilbert" | "metis"
"simple" — contiguous IDs, no optimisation.
"rcm" — Reverse Cuthill-McKee (bandwidth reduction).
"hilbert" — Hilbert space-filling curve (cache locality).
"metis" — METIS graph-partitioner ordering.
base : int
Starting ID (default 1 = OpenSees / Abaqus convention).
Returns
RenumberResult
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def renumber(
self,
dim: int = 2,
*,
method: str = "rcm",
base: int = 1,
) -> RenumberResult:
"""Renumber nodes and elements in the Gmsh model.
After this call every Gmsh query returns solver-ready contiguous
IDs. Call **once**, before extracting FEM data with
:meth:`~_Queries.get_fem_data`.
Parameters
----------
dim : int
Element dimension used to compute bandwidth and to collect
element tags for renumbering.
method : ``"simple"`` | ``"rcm"`` | ``"hilbert"`` | ``"metis"``
``"simple"`` — contiguous IDs, no optimisation.
``"rcm"`` — Reverse Cuthill-McKee (bandwidth reduction).
``"hilbert"`` — Hilbert space-filling curve (cache locality).
``"metis"`` — METIS graph-partitioner ordering.
base : int
Starting ID (default 1 = OpenSees / Abaqus convention).
Returns
-------
RenumberResult
"""
from ._fem_extract import extract_raw
from .FEMData import _compute_bandwidth
from ._fem_factory import _build_element_groups
# 1. Bandwidth BEFORE ────────────────────────────────────
raw = extract_raw(dim=dim)
groups = _build_element_groups(raw['groups'])
bw_before = _compute_bandwidth(groups)
n_nodes = len(raw['node_tags'])
n_elems = len(raw['elem_tags'])
# 2. Node renumbering ────────────────────────────────────
if method == "simple":
self._renumber_nodes_simple(base)
elif method in _METHOD_MAP:
old, new = gmsh.model.mesh.computeRenumbering(
method=_METHOD_MAP[method])
gmsh.model.mesh.renumberNodes(
oldTags=list(old), newTags=list(new))
else:
raise ValueError(
f"Unknown method {method!r}. "
f"Use 'simple', 'rcm', 'hilbert', or 'metis'.")
# 3. Element renumbering (always simple contiguous) ──────
self._renumber_elements_simple(dim, base)
# 4. Bandwidth AFTER ─────────────────────────────────────
raw_after = extract_raw(dim=dim)
groups_after = _build_element_groups(raw_after['groups'])
bw_after = _compute_bandwidth(groups_after)
result = RenumberResult(
method=method,
n_nodes=n_nodes,
n_elements=n_elems,
bandwidth_before=bw_before,
bandwidth_after=bw_after,
)
self._mesh._log(
f"renumber(method={method!r}, dim={dim}): "
f"{n_nodes} nodes, {n_elems} elements, "
f"bw {bw_before}\u2192{bw_after}")
return result
|
partition
partition(n_parts: int) -> PartitionInfo
Partition the mesh into n_parts sub-domains (METIS).
Must be called after g.mesh.generation.generate().
Parameters
n_parts : int
Number of partitions (>= 1).
Returns
PartitionInfo
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def partition(self, n_parts: int) -> PartitionInfo:
"""Partition the mesh into *n_parts* sub-domains (METIS).
Must be called after ``g.mesh.generation.generate()``.
Parameters
----------
n_parts : int
Number of partitions (>= 1).
Returns
-------
PartitionInfo
"""
if n_parts < 1:
raise ValueError(f"n_parts must be >= 1, got {n_parts}")
gmsh.model.mesh.partition(n_parts)
info = self._gather_partition_info()
self._mesh._log(f"partition(n_parts={n_parts})")
return info
|
partition_explicit
partition_explicit(n_parts: int, elem_tags: list[int], parts: list[int]) -> PartitionInfo
Partition with an explicit per-element assignment.
Parameters
n_parts : int
Total number of partitions declared.
elem_tags : list[int]
Element tags to assign.
parts : list[int]
Parallel list of 1-based partition IDs.
Returns
PartitionInfo
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def partition_explicit(
self,
n_parts: int,
elem_tags: list[int],
parts: list[int],
) -> PartitionInfo:
"""Partition with an explicit per-element assignment.
Parameters
----------
n_parts : int
Total number of partitions declared.
elem_tags : list[int]
Element tags to assign.
parts : list[int]
Parallel list of 1-based partition IDs.
Returns
-------
PartitionInfo
"""
if len(elem_tags) != len(parts):
raise ValueError(
f"len(elem_tags)={len(elem_tags)} != "
f"len(parts)={len(parts)}")
gmsh.model.mesh.partition(
n_parts, elementTags=elem_tags, partitions=parts)
info = self._gather_partition_info()
self._mesh._log(
f"partition_explicit(n_parts={n_parts}, "
f"n_elements={len(elem_tags)})")
return info
|
unpartition
unpartition() -> '_Partitioning'
Remove the partition structure and restore a monolithic mesh.
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def unpartition(self) -> "_Partitioning":
"""Remove the partition structure and restore a monolithic mesh."""
gmsh.model.mesh.unpartition()
self._mesh._log("unpartition()")
return self
|
n_partitions
Return the current number of partitions (0 if not partitioned).
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def n_partitions(self) -> int:
"""Return the current number of partitions (0 if not partitioned)."""
return gmsh.model.getNumberOfPartitions()
|
summary
Concise text summary of the partition state.
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def summary(self) -> str:
"""Concise text summary of the partition state."""
n = self.n_partitions()
model_name = getattr(
getattr(self._mesh, '_parent', None), 'name', '?')
if n == 0:
return f"Partitioning(model={model_name!r}): not partitioned"
lines = [
f"Partitioning(model={model_name!r}): {n} partition(s)"]
df = self.entity_table()
if not df.empty:
partitioned = df[df['partitions'] != '']
counts = (
partitioned
.reset_index()
.groupby('dim')
.size()
.rename(index={
0: 'points', 1: 'curves',
2: 'surfaces', 3: 'volumes'}))
for dim_label, count in counts.items():
lines.append(
f" {dim_label:10s}: {count} partitioned entities")
return "\n".join(lines)
|
entity_table
entity_table(dim: int = -1) -> 'pd.DataFrame'
DataFrame of all model entities and their partition membership.
Parameters
dim : int
Restrict to a single dimension (-1 = all).
Returns
pd.DataFrame
Columns: dim, tag, partitions,
parent_dim, parent_tag.
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def entity_table(self, dim: int = -1) -> "pd.DataFrame":
"""DataFrame of all model entities and their partition membership.
Parameters
----------
dim : int
Restrict to a single dimension (``-1`` = all).
Returns
-------
pd.DataFrame
Columns: ``dim``, ``tag``, ``partitions``,
``parent_dim``, ``parent_tag``.
"""
import pandas as pd
rows: list[dict] = []
entities = (
gmsh.model.getEntities(dim=dim)
if dim != -1
else gmsh.model.getEntities())
for ent_dim, ent_tag in entities:
try:
parts = list(gmsh.model.getPartitions(ent_dim, ent_tag))
except Exception:
parts = []
try:
p_dim, p_tag = gmsh.model.getParent(ent_dim, ent_tag)
except Exception:
p_dim, p_tag = -1, -1
rows.append({
'dim': ent_dim,
'tag': ent_tag,
'partitions': ", ".join(str(p) for p in parts),
'parent_dim': p_dim,
'parent_tag': p_tag,
})
if not rows:
return pd.DataFrame(
columns=['dim', 'tag', 'partitions',
'parent_dim', 'parent_tag'])
return pd.DataFrame(rows).set_index(['dim', 'tag'])
|
save
save(path: Path | str, *, one_file_per_partition: bool = False, create_topology: bool = False, create_physicals: bool = True) -> '_Partitioning'
Write the partitioned mesh to file(s).
Parameters
path : Path or str
Output file path (format inferred from extension).
one_file_per_partition : bool
Write one file per partition alongside the combined file.
create_topology : bool
Pass to Mesh.PartitionCreateTopology.
create_physicals : bool
Pass to Mesh.PartitionCreatePhysicals.
Returns
self — for chaining
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def save(
self,
path: Path | str,
*,
one_file_per_partition: bool = False,
create_topology: bool = False,
create_physicals: bool = True,
) -> "_Partitioning":
"""Write the partitioned mesh to file(s).
Parameters
----------
path : Path or str
Output file path (format inferred from extension).
one_file_per_partition : bool
Write one file per partition alongside the combined file.
create_topology : bool
Pass to ``Mesh.PartitionCreateTopology``.
create_physicals : bool
Pass to ``Mesh.PartitionCreatePhysicals``.
Returns
-------
self — for chaining
"""
path = Path(path)
gmsh.option.setNumber(
"Mesh.PartitionCreateTopology", int(create_topology))
gmsh.option.setNumber(
"Mesh.PartitionCreatePhysicals", int(create_physicals))
gmsh.option.setNumber(
"Mesh.PartitionSplitMeshFiles", int(one_file_per_partition))
gmsh.write(str(path))
self._mesh._log(
f"save({path}, "
f"one_file_per_partition={one_file_per_partition})")
return self
|
Fluent selection — g.mesh_selection.select()
g.mesh_selection (a session attribute,
MeshSelectionSet)
is the live-mesh entry of the unified, daisy-chainable
selection idiom. select() returns a
MeshSelection
(point family, bi-level) with .ids / .coords / .result() and the
live-engine-only .save_as(name) terminal. The former
add_nodes / add_elements spatial registrars (and from_geometric)
have been removed; persist a selection with
.select(...).save_as(name) (live engine), or register explicit ids
with the retained g.mesh_selection.add(dim, ids, name=) /
g.mesh_selection.from_physical(...). filter_set / sort_set /
union / intersection / difference remain.
node_set = (g.mesh_selection.select() # node level
.in_box((0, 0, 0), (1, 1, 1)) # half-open [lo, hi)
.on_plane((0, 0, 0), (0, 0, 1), tol=1e-9)
.save_as("base")) # live-engine persist
hexes = g.mesh_selection.select(level="element", dim=3).in_box(
lo, hi, inclusive=True).ids
S2 — point-family box is half-open
The point-family in_box(lo, hi) is half-open [lo, hi) (to
match results). Pass inclusive=True to get a closed
[lo, hi] box. See the changelog for the
migration note.
See Selection for the full idiom and the
removed-vs-retained migration table.
Supporting types
apeGmsh.mesh.PhysicalGroups.PhysicalGroups
PhysicalGroups(parent: SessionProtocol)
Bases: _HasLogging
Physical-group composite attached to a apeGmsh instance as
g.physical.
Wraps the physical-group subset of the Gmsh Python API with a clean,
method-chaining interface organised into:
- Creation —
add, add_point, add_curve,
add_surface, add_volume
- Naming —
set_name, remove_name
- Removal —
remove, remove_all
- Queries —
get_all, get_entities, get_groups_for_entity,
get_name, get_tag, summary
- Mesh nodes—
get_nodes
All mutating methods return self for chaining::
(g.physical
.add_surface([s_inlet], name="Inlet")
.add_surface([s1, s2], name="Wall")
.add_volume([vol], name="Fluid"))
Parameters
parent : _SessionBase
The owning instance — used for _verbose.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def __init__(self, parent: _SessionBase) -> None:
self._parent = parent
|
add
add(dim: int, tags, *, name: str = '', tag: Tag = -1) -> Tag
Create or append to a physical group.
If name matches an existing PG at this dim, the new tags
are merged into it (upsert). Otherwise a new PG is created.
Parameters
dim : entity dimension (0 = points, 1 = curves, 2 = surfaces,
3 = volumes)
tags : entity tags — accepts int, str (label or PG name),
(dim, tag) tuples, or a list mixing all of these.
String references are resolved via g.labels first,
then g.physical.
name : human-readable name. If a PG with this name already
exists at dim, the new entities are appended. A
physical-group name maps to exactly one dimension —
reusing name at a different dim raises ValueError.
tag : requested physical-group tag (-1 = auto-assign).
Ignored when appending to an existing named PG.
Returns
Tag the physical-group tag (existing or newly created)
Raises
ValueError
If name is already used by a physical group at a
different dimension. Multi-dimensional physical groups
are not supported — pick a distinct name per dimension.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def add(
self,
dim : int,
tags,
*,
name : str = "",
tag : Tag = -1,
) -> Tag:
"""Create or append to a physical group.
If *name* matches an existing PG at this *dim*, the new *tags*
are merged into it (upsert). Otherwise a new PG is created.
Parameters
----------
dim : entity dimension (0 = points, 1 = curves, 2 = surfaces,
3 = volumes)
tags : entity tags — accepts ``int``, ``str`` (label or PG name),
``(dim, tag)`` tuples, or a list mixing all of these.
String references are resolved via ``g.labels`` first,
then ``g.physical``.
name : human-readable name. If a PG with this name already
exists at *dim*, the new entities are appended. A
physical-group name maps to exactly one dimension —
reusing *name* at a different dim raises ``ValueError``.
tag : requested physical-group tag (``-1`` = auto-assign).
Ignored when appending to an existing named PG.
Returns
-------
Tag the physical-group tag (existing or newly created)
Raises
------
ValueError
If *name* is already used by a physical group at a
different dimension. Multi-dimensional physical groups
are not supported — pick a distinct name per dimension.
"""
from typing import cast
from apeGmsh.core._helpers import resolve_to_tags
if isinstance(tags, (str, int)):
tags = [tags]
# self._parent honours the SessionProtocol structural contract
# that resolve_to_tags requires — cast to the nominal type
# mypy expects.
from apeGmsh._session import _SessionBase
resolved = resolve_to_tags(
tags, dim=dim, session=cast(_SessionBase, self._parent),
)
# Upsert: if a PG with this name already exists, merge
if name:
conflict = [
d for d in (0, 1, 2, 3)
if d != dim and self.get_tag(d, name) is not None
]
if conflict:
raise ValueError(
f"Physical group {name!r} already exists at "
f"dim={conflict[0]}. A physical-group name maps to "
f"a single dimension — use a distinct name for the "
f"dim={dim} entities (multi-dimensional physical "
f"groups are not supported)."
)
existing_tag = self.get_tag(dim, name)
if existing_tag is not None:
old_ents = list(
gmsh.model.getEntitiesForPhysicalGroup(
dim, existing_tag))
combined = sorted(
set(old_ents) | set(int(t) for t in resolved))
gmsh.model.removePhysicalGroups(
dimTags=[(dim, existing_tag)])
pg_tag = gmsh.model.addPhysicalGroup(
dim, combined, tag=existing_tag)
gmsh.model.setPhysicalName(dim, pg_tag, name)
n_new = len(combined) - len(old_ents)
self._log(
f"add(dim={dim}, name={name!r}): appended "
f"{n_new} entity(ies) -> {len(combined)} total")
return pg_tag
pg_tag = gmsh.model.addPhysicalGroup(dim, resolved, tag=tag)
if name:
gmsh.model.setPhysicalName(dim, pg_tag, name)
self._log(
f"add(dim={dim}, entities={tags}) -> pg_tag={pg_tag}"
+ (f", name={name!r}" if name else "")
)
return pg_tag
|
add_point
add_point(tags: list[Tag], *, name: str = '', tag: Tag = -1) -> PhysicalGroups
Shorthand: add(dim=0, tags, ...) — returns self for chaining.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def add_point(self, tags: list[Tag], *, name: str = "", tag: Tag = -1) -> PhysicalGroups:
"""Shorthand: ``add(dim=0, tags, ...)`` — returns ``self`` for chaining."""
self.add(0, tags, name=name, tag=tag)
return self
|
add_curve
add_curve(tags: list[Tag], *, name: str = '', tag: Tag = -1) -> PhysicalGroups
Shorthand: add(dim=1, tags, ...) — returns self for chaining.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def add_curve(self, tags: list[Tag], *, name: str = "", tag: Tag = -1) -> PhysicalGroups:
"""Shorthand: ``add(dim=1, tags, ...)`` — returns ``self`` for chaining."""
self.add(1, tags, name=name, tag=tag)
return self
|
add_surface
add_surface(tags: list[Tag], *, name: str = '', tag: Tag = -1) -> PhysicalGroups
Shorthand: add(dim=2, tags, ...) — returns self for chaining.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def add_surface(self, tags: list[Tag], *, name: str = "", tag: Tag = -1) -> PhysicalGroups:
"""Shorthand: ``add(dim=2, tags, ...)`` — returns ``self`` for chaining."""
self.add(2, tags, name=name, tag=tag)
return self
|
add_volume
add_volume(tags: list[Tag], *, name: str = '', tag: Tag = -1) -> PhysicalGroups
Shorthand: add(dim=3, tags, ...) — returns self for chaining.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def add_volume(self, tags: list[Tag], *, name: str = "", tag: Tag = -1) -> PhysicalGroups:
"""Shorthand: ``add(dim=3, tags, ...)`` — returns ``self`` for chaining."""
self.add(3, tags, name=name, tag=tag)
return self
|
from_label
from_label(label_name: str, *, name: str | None = None, dim: int | None = None) -> Tag
Create (or append to) a PG from a label's entities.
Parameters
label_name : str
Label name (without _label: prefix).
name : str, optional
PG name. Defaults to the label name.
dim : int, optional
Dimension. If None, inferred from the label.
Returns
Tag the physical-group tag
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def from_label(
self,
label_name: str,
*,
name: str | None = None,
dim: int | None = None,
) -> Tag:
"""Create (or append to) a PG from a label's entities.
Parameters
----------
label_name : str
Label name (without ``_label:`` prefix).
name : str, optional
PG name. Defaults to the label name.
dim : int, optional
Dimension. If ``None``, inferred from the label.
Returns
-------
Tag the physical-group tag
"""
labels = getattr(self._parent, 'labels', None)
if labels is None:
raise RuntimeError("No labels composite on session.")
ent_tags = labels.entities(label_name, dim=dim)
# Infer dim from the label's PG
if dim is None:
for d in range(4):
for pg_dim, pg_tag in gmsh.model.getPhysicalGroups(d):
from apeGmsh._kernel._label_prefix import add_prefix
if gmsh.model.getPhysicalName(pg_dim, pg_tag) == add_prefix(label_name):
dim = pg_dim
break
if dim is not None:
break
if dim is None:
raise KeyError(
f"Cannot infer dimension for label {label_name!r}.")
pg_name = name if name is not None else label_name
return self.add(dim, ent_tags, name=pg_name)
|
from_labels
from_labels(label_names: list[str], *, name: str, dim: int | None = None) -> Tag
Create (or append to) a PG from multiple labels (union).
Parameters
label_names : list[str]
Label names to combine.
name : str
PG name for the combined group.
dim : int, optional
Dimension. If None, inferred from the first label.
Returns
Tag the physical-group tag
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def from_labels(
self,
label_names: list[str],
*,
name: str,
dim: int | None = None,
) -> Tag:
"""Create (or append to) a PG from multiple labels (union).
Parameters
----------
label_names : list[str]
Label names to combine.
name : str
PG name for the combined group.
dim : int, optional
Dimension. If ``None``, inferred from the first label.
Returns
-------
Tag the physical-group tag
"""
labels = getattr(self._parent, 'labels', None)
if labels is None:
raise RuntimeError("No labels composite on session.")
all_tags: list[int] = []
inferred_dim = dim
for lbl in label_names:
ent_tags = labels.entities(lbl, dim=dim)
all_tags.extend(ent_tags)
if inferred_dim is None:
# Infer from first label
for d in range(4):
for pg_dim, pg_tag in gmsh.model.getPhysicalGroups(d):
from apeGmsh._kernel._label_prefix import add_prefix
if gmsh.model.getPhysicalName(pg_dim, pg_tag) == add_prefix(lbl):
inferred_dim = pg_dim
break
if inferred_dim is not None:
break
if inferred_dim is None:
raise KeyError(
f"Cannot infer dimension for labels {label_names!r}.")
return self.add(inferred_dim, sorted(set(all_tags)), name=name)
|
set_name
set_name(dim: int, tag: Tag, name: str) -> PhysicalGroups
Assign (or rename) the label of an existing physical group.
Parameters
dim : dimension of the physical group
tag : physical-group tag
name : new label
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def set_name(self, dim: int, tag: Tag, name: str) -> PhysicalGroups:
"""
Assign (or rename) the label of an existing physical group.
Parameters
----------
dim : dimension of the physical group
tag : physical-group tag
name : new label
"""
gmsh.model.setPhysicalName(dim, tag, name)
self._log(f"set_name(dim={dim}, tag={tag}, name={name!r})")
return self
|
remove_name
remove_name(name: str) -> PhysicalGroups
Remove the name-to-tag mapping for name.
The physical group itself is not deleted — only the name entry.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def remove_name(self, name: str) -> PhysicalGroups:
"""
Remove the name-to-tag mapping for *name*.
The physical group itself is not deleted — only the name entry.
"""
gmsh.model.removePhysicalName(name)
self._log(f"remove_name({name!r})")
return self
|
remove
remove(dim_tags: list[DimTag]) -> PhysicalGroups
Remove specific physical groups.
Parameters
dim_tags : [(dim, pg_tag), ...] pairs identifying groups to remove
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def remove(self, dim_tags: list[DimTag]) -> PhysicalGroups:
"""
Remove specific physical groups.
Parameters
----------
dim_tags : ``[(dim, pg_tag), ...]`` pairs identifying groups to remove
"""
gmsh.model.removePhysicalGroups(dimTags=dim_tags)
self._log(f"remove({dim_tags})")
return self
|
remove_all
remove_all() -> PhysicalGroups
Remove every physical group in the current model.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def remove_all(self) -> PhysicalGroups:
"""Remove every physical group in the current model."""
gmsh.model.removePhysicalGroups()
self._log("remove_all()")
return self
|
get_all
get_all(dim: int = -1) -> list[DimTag]
Return all user-facing physical groups as (dim, tag)
pairs. Internal label PGs (Tier 1 naming, prefixed with
_label:) are filtered out — use g.labels.get_all()
to see those.
Parameters
dim : filter to a single dimension (-1 = all)
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def get_all(self, dim: int = -1) -> list[DimTag]:
"""
Return all **user-facing** physical groups as ``(dim, tag)``
pairs. Internal label PGs (Tier 1 naming, prefixed with
``_label:``) are filtered out — use ``g.labels.get_all()``
to see those.
Parameters
----------
dim : filter to a single dimension (``-1`` = all)
"""
return [
(d, t) for d, t in gmsh.model.getPhysicalGroups(dim=dim)
if not is_label_pg(gmsh.model.getPhysicalName(d, t))
]
|
get_entities
get_entities(dim: int, tag: Tag) -> list[Tag]
Return the model-entity tags contained in a physical group.
Parameters
dim : dimension of the physical group
tag : physical-group tag
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def get_entities(self, dim: int, tag: Tag) -> list[Tag]:
"""
Return the model-entity tags contained in a physical group.
Parameters
----------
dim : dimension of the physical group
tag : physical-group tag
"""
return list(gmsh.model.getEntitiesForPhysicalGroup(dim, tag))
|
entities
entities(name_or_tag, *, dim: int | None = None) -> list[Tag]
Resolve a physical group to its entity tags — by name or by tag.
Parameters
name_or_tag : str | int
Physical group name, or the raw PG tag (dim required).
dim : int | None, optional
Dimension to search. If omitted and name_or_tag is a string,
all dimensions are searched. A physical-group name maps to a
single dimension; if a legacy model nonetheless carries the
name at multiple dims this raises — pass dim= to read
one slice.
Returns
list[Tag]
Flat list of model-entity tags contained in the group.
Raises
KeyError
If no physical group with that name exists.
ValueError
If name_or_tag is a string, dim is omitted, and the
name matches physical groups at more than one dimension.
TypeError
If name_or_tag is an int but dim is not provided.
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def entities(
self,
name_or_tag,
*,
dim: int | None = None,
) -> list[Tag]:
"""
Resolve a physical group to its entity tags — by **name** or by tag.
Parameters
----------
name_or_tag : str | int
Physical group name, or the raw PG tag (``dim`` required).
dim : int | None, optional
Dimension to search. If omitted and *name_or_tag* is a string,
all dimensions are searched. A physical-group name maps to a
single dimension; if a legacy model nonetheless carries the
name at multiple dims this **raises** — pass ``dim=`` to read
one slice.
Returns
-------
list[Tag]
Flat list of model-entity tags contained in the group.
Raises
------
KeyError
If no physical group with that name exists.
ValueError
If *name_or_tag* is a string, ``dim`` is omitted, and the
name matches physical groups at more than one dimension.
TypeError
If ``name_or_tag`` is an int but ``dim`` is not provided.
"""
if isinstance(name_or_tag, str):
if dim is None:
matches = [
d for d in (0, 1, 2, 3)
if self.get_tag(d, name_or_tag) is not None
]
if not matches:
raise KeyError(
f"No physical group named {name_or_tag!r} at any "
f"dimension. If this is a label, use "
f"g.labels.entities({name_or_tag!r}) or promote it "
f"with g.labels.promote_to_physical({name_or_tag!r})."
)
if len(matches) > 1:
raise ValueError(
f"Physical group {name_or_tag!r} exists at "
f"multiple dimensions {matches}. Multi-dimensional "
f"physical groups are not supported; pass `dim=` "
f"to read one slice."
)
d = matches[0]
pg_tag = self.get_tag(d, name_or_tag)
return list(gmsh.model.getEntitiesForPhysicalGroup(d, pg_tag))
pg_tag = self.get_tag(dim, name_or_tag)
if pg_tag is None:
raise KeyError(
f"No physical group named {name_or_tag!r} at dim={dim}. "
f"If this is a label, use g.labels.entities() or "
f"g.labels.promote_to_physical()."
)
return list(gmsh.model.getEntitiesForPhysicalGroup(dim, pg_tag))
# int / PG tag path
if dim is None:
raise TypeError(
"entities(): when passing a raw PG tag, `dim` must be given"
)
return list(gmsh.model.getEntitiesForPhysicalGroup(dim, int(name_or_tag)))
|
get_groups_for_entity
get_groups_for_entity(dim: int, tag: Tag) -> list[Tag]
Return the physical-group tags that contain a given model entity.
Parameters
dim : entity dimension
tag : model-entity tag
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def get_groups_for_entity(self, dim: int, tag: Tag) -> list[Tag]:
"""
Return the physical-group tags that contain a given model entity.
Parameters
----------
dim : entity dimension
tag : model-entity tag
"""
return list(gmsh.model.getPhysicalGroupsForEntity(dim, tag))
|
get_name
get_name(dim: int, tag: Tag) -> str
Return the name of a physical group, or "" if unnamed.
Parameters
dim : dimension of the physical group
tag : physical-group tag
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def get_name(self, dim: int, tag: Tag) -> str:
"""
Return the name of a physical group, or ``""`` if unnamed.
Parameters
----------
dim : dimension of the physical group
tag : physical-group tag
"""
return gmsh.model.getPhysicalName(dim, tag)
|
get_tag
get_tag(dim: int, name: str) -> Tag | None
Look up the tag of a named physical group.
Returns None if no group with that name and dimension exists.
Parameters
dim : dimension to search
name : human-readable label
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def get_tag(self, dim: int, name: str) -> Tag | None:
"""
Look up the tag of a named physical group.
Returns ``None`` if no group with that name and dimension exists.
Parameters
----------
dim : dimension to search
name : human-readable label
"""
for _, pg_tag in gmsh.model.getPhysicalGroups(dim=dim):
pg_name = gmsh.model.getPhysicalName(dim, pg_tag)
if is_label_pg(pg_name):
continue
if pg_name == name:
return pg_tag
return None
|
summary
summary() -> pd.DataFrame
Build a DataFrame describing every user-facing physical
group in the model. Internal label PGs are excluded.
Returns
pd.DataFrame indexed by (dim, pg_tag) with columns:
dim entity dimension
pg_tag physical-group tag
name label (empty string if unnamed)
n_entities number of model entities in the group
entity_tags comma-separated entity tags as a string
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def summary(self) -> pd.DataFrame:
"""
Build a DataFrame describing every **user-facing** physical
group in the model. Internal label PGs are excluded.
Returns
-------
pd.DataFrame indexed by ``(dim, pg_tag)`` with columns:
``dim`` entity dimension
``pg_tag`` physical-group tag
``name`` label (empty string if unnamed)
``n_entities`` number of model entities in the group
``entity_tags`` comma-separated entity tags as a string
"""
rows: list[dict] = []
for dim, pg_tag in gmsh.model.getPhysicalGroups():
name = gmsh.model.getPhysicalName(dim, pg_tag)
if is_label_pg(name):
continue
entities = gmsh.model.getEntitiesForPhysicalGroup(dim, pg_tag)
rows.append({
'dim' : dim,
'pg_tag' : pg_tag,
'name' : name,
'n_entities' : len(entities),
'entity_tags': ", ".join(str(t) for t in entities),
})
if not rows:
return pd.DataFrame(
columns=['dim', 'pg_tag', 'name', 'n_entities', 'entity_tags']
)
df = (
pd.DataFrame(rows)
.set_index(['dim', 'pg_tag'])
.sort_index()
)
if self._parent._verbose:
print("\n--- Physical Groups ---")
print(df.to_string())
return df
|
get_nodes
get_nodes(dim: int, tag: Tag) -> dict
Return the mesh nodes belonging to a physical group.
Must be called after mesh generation.
Parameters
dim : dimension of the physical group
tag : physical-group tag
Returns
dict
'tags' : ndarray(N,) — node tags
'coords' : ndarray(N, 3) — XYZ coordinates
Source code in src/apeGmsh/mesh/PhysicalGroups.py
| def get_nodes(
self,
dim: int,
tag: Tag,
) -> dict:
"""
Return the mesh nodes belonging to a physical group.
Must be called after mesh generation.
Parameters
----------
dim : dimension of the physical group
tag : physical-group tag
Returns
-------
dict
``'tags'`` : ndarray(N,) — node tags
``'coords'`` : ndarray(N, 3) — XYZ coordinates
"""
import numpy as np
node_tags, coords = gmsh.model.mesh.getNodesForPhysicalGroup(dim, tag)
result = {
'tags' : np.array(node_tags, dtype=np.int64),
'coords': np.array(coords).reshape(-1, 3),
}
name = gmsh.model.getPhysicalName(dim, tag) or str(tag)
self._log(
f"get_nodes(dim={dim}, pg={name!r}) -> {len(node_tags)} nodes"
)
return result
|
apeGmsh.mesh.MeshSelectionSet.MeshSelectionSet
MeshSelectionSet(parent: '_SessionBase')
Bases: _HasLogging
Post-mesh selection composite — complementary to PhysicalGroups.
Attached to g.mesh_selection by the session framework.
Stores sets of mesh node/element IDs, resolved by spatial queries
or explicit tag lists. Mirrors the PhysicalGroups API shape so
downstream consumers (solvers, FEMData) can treat both identically.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def __init__(self, parent: "_SessionBase") -> None:
self._parent = parent
self._sets: dict[DimTag, dict] = {}
self._next_tag: dict[int, int] = {0: 1, 1: 1, 2: 1, 3: 1}
|
add
add(dim: int, tags: list[int], *, name: str = '', tag: int = -1) -> int
Add a mesh selection set from explicit node/element IDs.
Parameters
dim : 0 for node set, 1/2/3 for element set
tags : list of node IDs (dim=0) or element IDs (dim>=1)
name : optional label
tag : explicit tag (auto-allocated if -1)
Returns
int — the allocated set tag
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def add(
self,
dim: int,
tags: list[int],
*,
name: str = "",
tag: int = -1,
) -> int:
"""Add a mesh selection set from explicit node/element IDs.
Parameters
----------
dim : 0 for node set, 1/2/3 for element set
tags : list of node IDs (dim=0) or element IDs (dim>=1)
name : optional label
tag : explicit tag (auto-allocated if -1)
Returns
-------
int — the allocated set tag
"""
t = self._alloc_tag(dim, tag)
all_ids, all_coords = self._get_mesh_nodes()
if dim == 0:
# Node set
arr = np.array(tags, dtype=np.int64)
mask = np.isin(all_ids, arr)
self._store_node_set(t, name, all_ids[mask], all_coords[mask])
self._log(f"add(dim=0, tag={t}, name='{name}') -> {int(mask.sum())} nodes")
else:
# Element set
elem_ids_all, conn_all = self._get_mesh_elements(dim)
arr = np.array(tags, dtype=np.int64)
mask = np.isin(elem_ids_all, arr)
sel_ids = elem_ids_all[mask]
sel_conn = conn_all[mask]
# Gather nodes from connectivity
used = set(int(n) for n in sel_conn.ravel() if n >= 0)
nmask = np.isin(all_ids, list(used))
self._store_element_set(
dim, t, name, sel_ids, sel_conn,
all_ids[nmask], all_coords[nmask],
)
self._log(f"add(dim={dim}, tag={t}, name='{name}') -> "
f"{len(sel_ids)} elements, {int(nmask.sum())} nodes")
return t
|
get_entities
get_entities(dim: int, tag: int) -> list[int]
Return node IDs (dim=0) or element IDs (dim>=1).
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def get_entities(self, dim: int, tag: int) -> list[int]:
"""Return node IDs (dim=0) or element IDs (dim>=1)."""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(f"No mesh selection (dim={dim}, tag={tag})")
if dim == 0:
return list(int(n) for n in info["node_ids"])
return list(int(e) for e in info["element_ids"])
|
get_nodes
get_nodes(dim: int, tag: int) -> dict
Return {'tags': ndarray, 'coords': ndarray(N,3)}.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def get_nodes(self, dim: int, tag: int) -> dict:
"""Return ``{'tags': ndarray, 'coords': ndarray(N,3)}``."""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(f"No mesh selection (dim={dim}, tag={tag})")
return {
"tags": np.asarray(info["node_ids"]).astype(object),
"coords": np.asarray(info["node_coords"], dtype=np.float64),
}
|
get_elements
get_elements(dim: int, tag: int) -> dict
Return {'element_ids': ndarray, 'connectivity': ndarray(E,npe)}.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def get_elements(self, dim: int, tag: int) -> dict:
"""Return ``{'element_ids': ndarray, 'connectivity': ndarray(E,npe)}``."""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(f"No mesh selection (dim={dim}, tag={tag})")
eids = info.get("element_ids")
conn = info.get("connectivity")
if eids is None or conn is None:
name = info.get("name", f"(dim={dim}, tag={tag})")
raise ValueError(
f"Mesh selection '{name}' has no element data. "
f"Element data is only available for dim >= 1 sets."
)
return {
"element_ids": np.asarray(eids).astype(object),
"connectivity": np.asarray(conn).astype(object),
}
|
union
union(dim: int, tag_a: int, tag_b: int, *, name: str = '', tag: int = -1) -> int
Create a new set = A ∪ B.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def union(
self, dim: int, tag_a: int, tag_b: int,
*, name: str = "", tag: int = -1,
) -> int:
"""Create a new set = A ∪ B."""
a = self._sets.get((dim, tag_a))
b = self._sets.get((dim, tag_b))
if a is None or b is None:
raise KeyError(f"Set (dim={dim}, tag={tag_a}) or tag={tag_b} not found")
if dim == 0:
ids = np.union1d(a["node_ids"], b["node_ids"])
return self.add(0, list(ids), name=name, tag=tag)
ids = np.union1d(a["element_ids"], b["element_ids"])
return self.add(dim, list(ids), name=name, tag=tag)
|
intersection
intersection(dim: int, tag_a: int, tag_b: int, *, name: str = '', tag: int = -1) -> int
Create a new set = A ∩ B.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def intersection(
self, dim: int, tag_a: int, tag_b: int,
*, name: str = "", tag: int = -1,
) -> int:
"""Create a new set = A ∩ B."""
a = self._sets.get((dim, tag_a))
b = self._sets.get((dim, tag_b))
if a is None or b is None:
raise KeyError(f"Set (dim={dim}, tag={tag_a}) or tag={tag_b} not found")
if dim == 0:
ids = np.intersect1d(a["node_ids"], b["node_ids"])
return self.add(0, list(ids), name=name, tag=tag)
ids = np.intersect1d(a["element_ids"], b["element_ids"])
return self.add(dim, list(ids), name=name, tag=tag)
|
difference
difference(dim: int, tag_a: int, tag_b: int, *, name: str = '', tag: int = -1) -> int
Create a new set = A \ B.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def difference(
self, dim: int, tag_a: int, tag_b: int,
*, name: str = "", tag: int = -1,
) -> int:
"""Create a new set = A \\ B."""
a = self._sets.get((dim, tag_a))
b = self._sets.get((dim, tag_b))
if a is None or b is None:
raise KeyError(f"Set (dim={dim}, tag={tag_a}) or tag={tag_b} not found")
if dim == 0:
ids = np.setdiff1d(a["node_ids"], b["node_ids"])
return self.add(0, list(ids), name=name, tag=tag)
ids = np.setdiff1d(a["element_ids"], b["element_ids"])
return self.add(dim, list(ids), name=name, tag=tag)
|
from_physical
from_physical(dim: int, name_or_tag: str | int, *, ms_name: str = '', ms_tag: int = -1) -> int
Import a physical group as a mesh selection set.
Parameters
dim : dimension of the physical group
name_or_tag : physical group name or tag
ms_name : name for the new mesh selection
ms_tag : tag for the new mesh selection (-1 = auto)
Returns
int — mesh selection tag
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def from_physical(
self, dim: int, name_or_tag: str | int,
*, ms_name: str = "", ms_tag: int = -1,
) -> int:
"""Import a physical group as a mesh selection set.
Parameters
----------
dim : dimension of the physical group
name_or_tag : physical group name or tag
ms_name : name for the new mesh selection
ms_tag : tag for the new mesh selection (-1 = auto)
Returns
-------
int — mesh selection tag
"""
# Resolve name -> tag
if isinstance(name_or_tag, str):
for pg_dim, pg_tag in gmsh.model.getPhysicalGroups(dim):
try:
if gmsh.model.getPhysicalName(pg_dim, pg_tag) == name_or_tag:
name_or_tag = pg_tag
break
except Exception:
pass
else:
raise KeyError(f"Physical group '{name_or_tag}' not found at dim={dim}")
pg_tag = int(name_or_tag)
node_tags, coords = gmsh.model.mesh.getNodesForPhysicalGroup(dim, pg_tag)
t = self._alloc_tag(0, ms_tag)
nids = np.asarray(node_tags, dtype=np.int64)
ncoords = np.asarray(coords, dtype=np.float64).reshape(-1, 3)
label = ms_name or gmsh.model.getPhysicalName(dim, pg_tag)
self._store_node_set(t, label, nids, ncoords)
self._log(f"from_physical(dim={dim}, pg_tag={pg_tag}) -> "
f"node set tag={t}, {len(nids)} nodes")
return t
|
filter_set
filter_set(dim: int, tag: int, *, name: str = '', new_tag: int = -1, on_plane: tuple | None = None, in_box: tuple | list | None = None, in_sphere: tuple | None = None, closest_to: tuple | None = None, count: int = 1, predicate: Callable[[ndarray], ndarray] | None = None, inclusive: bool = False) -> int
Refine an existing set with spatial filters -> create a new set.
For node sets, filters apply to node coordinates. For element
sets, filters apply to element centroids. All filters are
AND-combined.
Parameters
dim, tag : source set identifier
name, new_tag : identifier for the resulting set
on_plane, in_box, in_sphere, closest_to, predicate :
same semantics as :meth:add_nodes (in_box is half-open
on the upper side by default)
inclusive : if True, in_box uses a closed upper bound
(restores the pre-S2 closed-closed behavior).
Returns
int — tag of the new (filtered) set
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def filter_set(
self,
dim: int,
tag: int,
*,
name: str = "",
new_tag: int = -1,
on_plane: tuple | None = None,
in_box: tuple | list | None = None,
in_sphere: tuple | None = None,
closest_to: tuple | None = None,
count: int = 1,
predicate: Callable[[np.ndarray], np.ndarray] | None = None,
inclusive: bool = False,
) -> int:
"""Refine an existing set with spatial filters -> create a new set.
For node sets, filters apply to node coordinates. For element
sets, filters apply to element centroids. All filters are
AND-combined.
Parameters
----------
dim, tag : source set identifier
name, new_tag : identifier for the resulting set
on_plane, in_box, in_sphere, closest_to, predicate :
same semantics as :meth:`add_nodes` (``in_box`` is half-open
on the upper side by default)
inclusive : if True, ``in_box`` uses a closed upper bound
(restores the pre-S2 closed-closed behavior).
Returns
-------
int — tag of the new (filtered) set
"""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(
f"No mesh selection (dim={dim}, tag={tag}). "
f"Available: {self.get_all()}"
)
if dim == 0:
ids = np.asarray(info["node_ids"])
coords = np.asarray(info["node_coords"], dtype=np.float64)
else:
# Element set: centroids drive the filtering
elem_ids = np.asarray(info.get("element_ids", []))
conn = np.asarray(info.get("connectivity"))
node_ids, node_coords = self._get_mesh_nodes()
id_to_idx = {int(n): i for i, n in enumerate(node_ids)}
coords = _flt.element_centroids(conn, id_to_idx, node_coords)
ids = elem_ids
mask = np.ones(len(ids), dtype=bool)
if on_plane is not None:
axis, value = on_plane[0], on_plane[1]
atol = on_plane[2] if len(on_plane) > 2 else 1e-6
mask &= _flt.nodes_on_plane(coords, axis, value, atol)
if in_box is not None:
mask &= _flt.nodes_in_box(coords, in_box, inclusive=inclusive)
if in_sphere is not None:
cx, cy, cz, r = in_sphere
mask &= _flt.nodes_in_sphere(coords, (cx, cy, cz), r)
if closest_to is not None:
mask &= _flt.nodes_nearest(coords, closest_to, count)
if predicate is not None:
mask &= predicate(coords)
t = self._alloc_tag(dim, new_tag)
if dim == 0:
self._store_node_set(t, name, ids[mask], coords[mask])
else:
self._sets[(dim, t)] = {
"name": name,
"node_ids": np.unique(conn[mask].ravel()),
"node_coords": np.array([], dtype=np.float64).reshape(0, 3),
"element_ids": ids[mask],
"connectivity": conn[mask],
}
self._log(
f"filter_set(dim={dim}, src={tag}) -> tag={t}, "
f"{int(mask.sum())}/{len(mask)} kept"
)
return t
|
select
select(*, level: str = 'node', dim: int = 2, ids=None, name=None)
Start a daisy-chainable selection over the live mesh.
Returns a
:class:~apeGmsh.mesh._mesh_selection_chain.MeshSelectionChain
(point family) — the fluent equivalent of the eager
:meth:add_nodes / :meth:add_elements::
g.mesh_selection.select().in_box(lo, hi).on_plane(p, n, tol=1e-6)
g.mesh_selection.select(level="element", dim=3).in_box(lo, hi)
g.mesh_selection.select(ids=a) | g.mesh_selection.select(ids=b)
g.mesh_selection.select(name="base").in_sphere(c, r)
The chain seeds its atoms and then the standard point-family
verbs (in_box / on_plane / in_sphere /
nearest_to / where + | & - ^) narrow it, operating
on the same live-mesh coordinates the eager API uses (node
coords for level="node", element centroids for
level="element") — so
select().in_box(b).on_plane(p, n, tol=t) selects the same
nodes/elements as add_nodes(in_box=b, on_plane=(...)).
Parameters
level : "node" (default) — atoms are mesh node ids; or
"element" — atoms are element ids for dim.
dim : element dimension (1/2/3) used when
level == "element" (ignored for the node level);
mirrors :meth:add_elements's dim.
ids : optional explicit id list to seed from. Omitted (and
no name) → the full live-mesh node universe
(level="node") or the full live-mesh element universe
of dim (level="element"), exactly the universe
:meth:add_nodes / :meth:add_elements start from before
their filters.
name : optional name of an existing g.mesh_selection
set to seed from (its node ids for level="node", its
element ids for level="element"), so
select(name=N).<spatial> is the id-for-id fluent
equivalent of :meth:filter_set over that set. Mutually
exclusive with ids=. Resolution delegates verbatim
to the existing :meth:get_tag / :meth:get_nodes /
:meth:get_elements surface (no new resolver); an unknown
name fails loud.
Notes
.select() is additive: it does not register a set into
:attr:_sets, does not allocate a tag, and does not perturb
the eager API (name= only reads :attr:_sets).
MeshSelectionChain is imported deferred (mirrors
mesh/_mesh_structured.py); the chain module imports only
the package-root leaf apeGmsh._chain + numpy, so this adds
no eager cross-package edge
(tests/test_import_dag_polarity.py baseline unchanged).
name= seeds from an already-registered mesh-selection
set only. Seeding directly from a raw gmsh physical-group
name or an apeGmsh label is not a select() parameter:
MeshSelectionSet has no non-registering, non-reimplementing
resolver for those — its only PG bridge,
:meth:from_physical, registers a set + allocates a tag
(which select() must not do) and is node-only; label /
geometry resolution lives off MeshSelectionSet entirely,
and a mesh-selection name is deliberately not a
geometry-resolver tier (see
docs/plans/selection-unification.md §9 and
tests/test_resolution_contract.py). The supported,
existing-surface route is the documented two step
from_physical(dim, "PG", ms_name="foo") /
from_geometric(sel, name="foo") then
select(name="foo"). Persistence is now available on the
returned MeshSelection via .save_as(name)
(selection-unification-v2 P2-I): it registers the (already
narrowed) id set through the existing
:meth:MeshSelectionSet.add surface, so the named set
snapshots into fem.mesh_selection and round-trips via
FEMData HDF5 as selection= — the live-mesh engine is the
only context where the mutable mesh-selection store is
reachable (see ADR 0015).
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def select(
self,
*,
level: str = "node",
dim: int = 2,
ids=None,
name=None,
):
"""Start a daisy-chainable selection over the **live** mesh.
Returns a
:class:`~apeGmsh.mesh._mesh_selection_chain.MeshSelectionChain`
(point family) — the fluent equivalent of the eager
:meth:`add_nodes` / :meth:`add_elements`::
g.mesh_selection.select().in_box(lo, hi).on_plane(p, n, tol=1e-6)
g.mesh_selection.select(level="element", dim=3).in_box(lo, hi)
g.mesh_selection.select(ids=a) | g.mesh_selection.select(ids=b)
g.mesh_selection.select(name="base").in_sphere(c, r)
The chain seeds its atoms and then the standard point-family
verbs (``in_box`` / ``on_plane`` / ``in_sphere`` /
``nearest_to`` / ``where`` + ``| & - ^``) narrow it, operating
on the same live-mesh coordinates the eager API uses (node
coords for ``level="node"``, element centroids for
``level="element"``) — so
``select().in_box(b).on_plane(p, n, tol=t)`` selects the same
nodes/elements as ``add_nodes(in_box=b, on_plane=(...))``.
Parameters
----------
level : ``"node"`` (default) — atoms are mesh node ids; or
``"element"`` — atoms are element ids for ``dim``.
dim : element dimension (1/2/3) used when
``level == "element"`` (ignored for the node level);
mirrors :meth:`add_elements`'s ``dim``.
ids : optional explicit id list to seed from. Omitted (and
no ``name``) → the full live-mesh node universe
(``level="node"``) or the full live-mesh element universe
of ``dim`` (``level="element"``), exactly the universe
:meth:`add_nodes` / :meth:`add_elements` start from before
their filters.
name : optional name of an **existing** ``g.mesh_selection``
set to seed from (its node ids for ``level="node"``, its
element ids for ``level="element"``), so
``select(name=N).<spatial>`` is the id-for-id fluent
equivalent of :meth:`filter_set` over that set. Mutually
exclusive with ``ids=``. Resolution **delegates verbatim**
to the existing :meth:`get_tag` / :meth:`get_nodes` /
:meth:`get_elements` surface (no new resolver); an unknown
name fails loud.
Notes
-----
``.select()`` is **additive**: it does not register a set into
:attr:`_sets`, does not allocate a tag, and does not perturb
the eager API (``name=`` only *reads* :attr:`_sets`).
``MeshSelectionChain`` is imported **deferred** (mirrors
``mesh/_mesh_structured.py``); the chain module imports only
the package-root leaf ``apeGmsh._chain`` + numpy, so this adds
no eager cross-package edge
(``tests/test_import_dag_polarity.py`` baseline unchanged).
``name=`` seeds from an **already-registered** mesh-selection
set only. Seeding *directly* from a raw gmsh physical-group
name or an apeGmsh label is **not** a ``select()`` parameter:
``MeshSelectionSet`` has no non-registering, non-reimplementing
resolver for those — its only PG bridge,
:meth:`from_physical`, *registers* a set + allocates a tag
(which ``select()`` must not do) and is node-only; label /
geometry resolution lives off ``MeshSelectionSet`` entirely,
and a mesh-selection name is deliberately *not* a
geometry-resolver tier (see
``docs/plans/selection-unification.md`` §9 and
``tests/test_resolution_contract.py``). The supported,
existing-surface route is the documented two step
``from_physical(dim, "PG", ms_name="foo")`` /
``from_geometric(sel, name="foo")`` **then**
``select(name="foo")``. Persistence is now available on the
returned ``MeshSelection`` via ``.save_as(name)``
(selection-unification-v2 P2-I): it registers the (already
narrowed) id set through the existing
:meth:`MeshSelectionSet.add` surface, so the named set
snapshots into ``fem.mesh_selection`` and round-trips via
FEMData HDF5 as ``selection=`` — the live-mesh engine is the
only context where the mutable mesh-selection store is
reachable (see ADR 0015).
"""
# selection-unification-v2 P2-I (§6.1 STOP-2): return the v2
# terminal ``MeshSelection`` (legacy ``MeshSelectionChain`` left
# defined-but-unwired; P3 deletes it). ``engine_for`` is kept
# verbatim — the per-``(level, dim)`` memoised ``_LiveMeshEngine``
# singleton is what ``SelectionChain._compatible`` gates
# set-algebra identity on, and ``MeshSelection`` delegates the
# per-engine live-mesh read back to a ``MeshSelectionChain``
# built from this exact same engine (byte-faithful). Same
# deferred-import idiom; ``_mesh_selection`` imports only the
# package-root leaf ``_kernel.chain`` at load (one declared
# downward BASELINE triple; no new eager cross-package edge).
from ._live_engine import engine_for # deferred — plan §3
from ._mesh_selection import MeshSelection # deferred — plan §3
if level not in ("node", "element"):
raise ValueError(
f"select(level=) must be 'node' or 'element', "
f"got {level!r}."
)
if ids is not None and name is not None:
raise ValueError(
"select(ids=, name=) are mutually exclusive — pass an "
"explicit id list OR an existing selection-set name, "
"not both."
)
if level == "node":
eng = engine_for(self, "node", 0)
if ids is not None:
atoms = [int(n) for n in ids]
elif name is not None:
atoms = [int(n) for n in self._seed_ids_by_name(0, name)]
else:
all_ids, _ = self._get_mesh_nodes()
atoms = [int(n) for n in all_ids]
else:
d = int(dim)
eng = engine_for(self, "element", d)
if ids is not None:
atoms = [int(e) for e in ids]
elif name is not None:
atoms = [int(e) for e in self._seed_ids_by_name(d, name)]
else:
elem_ids, _ = self._get_mesh_elements(d)
atoms = [int(e) for e in elem_ids]
return MeshSelection(atoms, _engine=eng)
|
sort_set
sort_set(dim: int, tag: int, *, by: str = 'x', descending: bool = False) -> None
Sort the entries of a set in place by coordinate axis.
Parameters
dim, tag : set identifier
by : "x", "y", or "z" — axis to sort along
descending : reverse the order
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def sort_set(
self,
dim: int,
tag: int,
*,
by: str = "x",
descending: bool = False,
) -> None:
"""Sort the entries of a set in place by coordinate axis.
Parameters
----------
dim, tag : set identifier
by : "x", "y", or "z" — axis to sort along
descending : reverse the order
"""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(f"No mesh selection (dim={dim}, tag={tag}).")
axis_idx = {"x": 0, "y": 1, "z": 2}[by.lower()]
if dim == 0:
coords = np.asarray(info["node_coords"], dtype=np.float64)
order = np.argsort(coords[:, axis_idx])
if descending:
order = order[::-1]
info["node_ids"] = np.asarray(info["node_ids"])[order]
info["node_coords"] = coords[order]
else:
conn = np.asarray(info.get("connectivity"))
elem_ids = np.asarray(info.get("element_ids", []))
node_ids, node_coords = self._get_mesh_nodes()
id_to_idx = {int(n): i for i, n in enumerate(node_ids)}
cents = _flt.element_centroids(conn, id_to_idx, node_coords)
order = np.argsort(cents[:, axis_idx])
if descending:
order = order[::-1]
info["element_ids"] = elem_ids[order]
info["connectivity"] = conn[order]
|
summary
summary() -> pd.DataFrame
DataFrame describing all mesh selection sets.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def summary(self) -> pd.DataFrame:
"""DataFrame describing all mesh selection sets."""
rows: list[dict] = []
for (dim, t), info in sorted(self._sets.items()):
eids = info.get("element_ids")
rows.append({
"dim": dim,
"tag": t,
"name": info.get("name", ""),
"n_nodes": len(info["node_ids"]),
"n_elems": len(eids) if eids is not None else 0,
})
if not rows:
return pd.DataFrame(columns=["dim", "tag", "name", "n_nodes", "n_elems"])
return pd.DataFrame(rows).set_index(["dim", "tag"]).sort_index()
|
to_dataframe
to_dataframe(dim: int, tag: int) -> pd.DataFrame
Return a DataFrame of the entries of a single set.
For dim=0 (node set): columns [node_id, x, y, z].
For dim>0 (element set): columns [element_id, cx, cy, cz, n_nodes].
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def to_dataframe(self, dim: int, tag: int) -> pd.DataFrame:
"""Return a DataFrame of the entries of a single set.
For dim=0 (node set): columns ``[node_id, x, y, z]``.
For dim>0 (element set): columns ``[element_id, cx, cy, cz, n_nodes]``.
"""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(
f"No mesh selection (dim={dim}, tag={tag}). "
f"Available: {self.get_all()}"
)
if dim == 0:
return pd.DataFrame({
"node_id": np.asarray(info["node_ids"]),
"x": info["node_coords"][:, 0],
"y": info["node_coords"][:, 1],
"z": info["node_coords"][:, 2],
})
elem_ids = np.asarray(info.get("element_ids", []))
conn = np.asarray(info.get("connectivity"))
node_ids, node_coords = self._get_mesh_nodes()
id_to_idx = {int(n): i for i, n in enumerate(node_ids)}
cents = _flt.element_centroids(conn, id_to_idx, node_coords)
return pd.DataFrame({
"element_id": elem_ids,
"cx": cents[:, 0],
"cy": cents[:, 1],
"cz": cents[:, 2],
"n_nodes": [conn.shape[1]] * len(elem_ids),
})
|
apeGmsh.mesh.MeshSelectionSet.MeshSelectionStore
MeshSelectionStore(sets: dict[DimTag, dict])
Immutable snapshot of mesh selections captured at get_fem_data() time.
Accessed via fem.mesh_selection. Mirrors the query API of
:class:MeshSelectionSet and :class:PhysicalGroupSet.
Example
::
fem = g.mesh.queries.get_fem_data(dim=2)
fem.mesh_selection.get_all()
fem.mesh_selection.get_nodes(0, 1)
fem.mesh_selection.get_elements(2, 1)
fem.mesh_selection.summary()
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def __init__(self, sets: dict[DimTag, dict]) -> None:
self._sets = sets
|
get_nodes
get_nodes(dim: int, tag: int) -> dict
Return {'tags': ndarray, 'coords': ndarray(N,3)}.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def get_nodes(self, dim: int, tag: int) -> dict:
"""Return ``{'tags': ndarray, 'coords': ndarray(N,3)}``."""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(
f"No mesh selection (dim={dim}, tag={tag}). "
f"Available: {self.get_all()}"
)
return {
"tags": np.asarray(info["node_ids"]).astype(object),
"coords": np.asarray(info["node_coords"], dtype=np.float64),
}
|
get_elements
get_elements(dim: int, tag: int) -> dict
Return {'element_ids': ndarray, 'connectivity': ndarray(E,npe)}.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def get_elements(self, dim: int, tag: int) -> dict:
"""Return ``{'element_ids': ndarray, 'connectivity': ndarray(E,npe)}``."""
info = self._sets.get((dim, tag))
if info is None:
raise KeyError(
f"No mesh selection (dim={dim}, tag={tag}). "
f"Available: {self.get_all()}"
)
eids = info.get("element_ids")
conn = info.get("connectivity")
if eids is None or conn is None:
name = info.get("name", f"(dim={dim}, tag={tag})")
raise ValueError(
f"Mesh selection '{name}' has no element data. "
f"Element data is only available for dim >= 1 sets."
)
return {
"element_ids": np.asarray(eids).astype(object),
"connectivity": np.asarray(conn).astype(object),
}
|
names
names(dim: int = -1) -> list[str]
All non-empty selection names, optionally filtered by dim.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def names(self, dim: int = -1) -> list[str]:
"""All non-empty selection names, optionally filtered by dim."""
out = []
for (d, _), info in sorted(self._sets.items()):
n = info.get("name", "")
if n and (dim == -1 or d == dim):
out.append(n)
return out
|
node_ids
node_ids(name: str) -> np.ndarray
Node IDs for any selection matching name (any dim).
For dim=0 selections returns the explicit node IDs.
For dim>=1 selections returns the node IDs implied by the
elements' connectivity.
Raises KeyError if no selection matches name.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def node_ids(self, name: str) -> np.ndarray:
"""Node IDs for any selection matching ``name`` (any dim).
For ``dim=0`` selections returns the explicit node IDs.
For ``dim>=1`` selections returns the node IDs implied by the
elements' connectivity.
Raises ``KeyError`` if no selection matches ``name``.
"""
for (dim, tag), info in self._sets.items():
if info.get("name", "") == name:
return np.asarray(info["node_ids"], dtype=np.int64)
raise KeyError(
f"No mesh selection named {name!r}. "
f"Available: {self.names()}"
)
|
element_ids
element_ids(name: str) -> np.ndarray
Element IDs for an element-bearing selection matching name.
Searches dim>=1 selections only (dim=0 selections have
no element data).
Raises KeyError if no element-bearing selection matches.
Source code in src/apeGmsh/mesh/MeshSelectionSet.py
| def element_ids(self, name: str) -> np.ndarray:
"""Element IDs for an element-bearing selection matching ``name``.
Searches ``dim>=1`` selections only (``dim=0`` selections have
no element data).
Raises ``KeyError`` if no element-bearing selection matches.
"""
for (dim, tag), info in self._sets.items():
if dim < 1:
continue
if info.get("name", "") == name:
eids = info.get("element_ids")
if eids is None:
continue
return np.asarray(eids, dtype=np.int64)
raise KeyError(
f"No element-bearing mesh selection named {name!r}. "
f"Available (dim>=1): "
f"{self.names(1) + self.names(2) + self.names(3)}"
)
|
apeGmsh.mesh.MshLoader.MshLoader
MshLoader(parent: '_SessionBase | None' = None)
Bases: _HasLogging
Load .msh files and produce solver-ready :class:FEMData.
Can be used standalone via the :meth:load classmethod, or as a
composite on a apeGmsh / Assembly session via g.loader.
Parameters
parent : _SessionBase or None
The owning session when used as a composite. None when
used standalone.
Source code in src/apeGmsh/mesh/MshLoader.py
| def __init__(self, parent: "_SessionBase | None" = None) -> None:
self._parent = parent
|
load
classmethod
load(path: str | Path, *, dim: int = 2, verbose: bool = False) -> 'FEMData'
Load a .msh file and return a :class:FEMData.
Manages its own Gmsh session internally — no apeGmsh
instance, no begin()/end() needed. Supports MSH2
and MSH4 formats.
Parameters
path : str or Path
Path to the .msh file.
dim : int
Element dimension to extract (1 = lines, 2 = tri/quad,
3 = tet/hex). Default is 2.
verbose : bool
Print a summary of what was loaded.
Returns
FEMData
Self-contained solver-ready mesh data with physical
groups, mesh statistics, and connectivity.
Example
::
from apeGmsh import MshLoader, Numberer
fem = MshLoader.load("bridge.msh", dim=2)
print(fem.info)
print(fem.physical.summary())
numb = Numberer(fem)
data = numb.renumber(method="rcm")
Source code in src/apeGmsh/mesh/MshLoader.py
| @classmethod
def load(
cls,
path: str | Path,
*,
dim: int = 2,
verbose: bool = False,
) -> "FEMData":
"""
Load a ``.msh`` file and return a :class:`FEMData`.
Manages its own Gmsh session internally — no ``apeGmsh``
instance, no ``begin()``/``end()`` needed. Supports MSH2
and MSH4 formats.
Parameters
----------
path : str or Path
Path to the ``.msh`` file.
dim : int
Element dimension to extract (1 = lines, 2 = tri/quad,
3 = tet/hex). Default is 2.
verbose : bool
Print a summary of what was loaded.
Returns
-------
FEMData
Self-contained solver-ready mesh data with physical
groups, mesh statistics, and connectivity.
Example
-------
::
from apeGmsh import MshLoader, Numberer
fem = MshLoader.load("bridge.msh", dim=2)
print(fem.info)
print(fem.physical.summary())
numb = Numberer(fem)
data = numb.renumber(method="rcm")
"""
from .FEMData import FEMData
p = cls._validate_path(path)
fem = FEMData.from_msh(str(p), dim=dim)
cls._log_fem(fem, f"load({p.name!r})", verbose)
return fem
|
from_msh
from_msh(path: str | Path, *, dim: int = 2) -> 'FEMData'
Load a .msh file into the active Gmsh session.
The mesh is merged via gmsh.merge(), so all composites
(g.physical, g.plot, g.inspect, etc.) remain
usable afterwards.
Parameters
path : str or Path
Path to the .msh file.
dim : int
Element dimension to extract. Default is 2.
Returns
FEMData
Self-contained solver-ready mesh data.
Raises
FileNotFoundError
If path does not exist.
RuntimeError
If no Gmsh session is active (call g.begin() first).
Example
::
g = apeGmsh(model_name="imported")
g.begin()
fem = g.loader.from_msh("model.msh", dim=2)
print(fem.physical.summary())
g.end()
Source code in src/apeGmsh/mesh/MshLoader.py
| def from_msh(
self,
path: str | Path,
*,
dim: int = 2,
) -> "FEMData":
"""
Load a ``.msh`` file into the **active** Gmsh session.
The mesh is merged via ``gmsh.merge()``, so all composites
(``g.physical``, ``g.plot``, ``g.inspect``, etc.) remain
usable afterwards.
Parameters
----------
path : str or Path
Path to the ``.msh`` file.
dim : int
Element dimension to extract. Default is 2.
Returns
-------
FEMData
Self-contained solver-ready mesh data.
Raises
------
FileNotFoundError
If *path* does not exist.
RuntimeError
If no Gmsh session is active (call ``g.begin()`` first).
Example
-------
::
g = apeGmsh(model_name="imported")
g.begin()
fem = g.loader.from_msh("model.msh", dim=2)
print(fem.physical.summary())
g.end()
"""
from .FEMData import FEMData
p = self._validate_path(path)
if self._parent is None or not self._parent.is_active:
raise RuntimeError(
"No active Gmsh session. Call g.begin() first, "
"or use MshLoader.load() for standalone loading."
)
self._log(f"merging {p.name} ...")
gmsh.merge(str(p))
fem = FEMData.from_gmsh(dim=dim)
verbose = self._parent._verbose if self._parent else False
self._log_fem(fem, f"from_msh({p.name!r})", verbose)
return fem
|
Legacy
Partition below is the standalone, pre-composite class. New code
should use the live g.mesh.partitioning composite documented above.
apeGmsh.mesh.Partition.Partition
Partition(parent: SessionProtocol)
Bases: _HasLogging
Mesh-partitioning composite attached to a apeGmsh instance as
g.partition.
Wraps gmsh.model.mesh.partition / unpartition and the
partition-aware model queries exposed after partitioning.
Workflow
::
with apeGmsh(model_name="Cube") as g:
# build and mesh
g.model.geometry.add_box(0, 0, 0, 1, 1, 1)
g.model.sync()
g.mesh.generation.generate(3)
# auto-partition into 4 parts
g.partition.auto(4)
# inspect
print(g.partition.summary())
df = g.partition.entity_table()
# save — one combined file or one per partition
g.partition.save("cube_part.msh")
g.partition.save("cube", one_file_per_partition=True)
Or with an explicit element -> partition assignment::
elem_tags = [1, 2, 3, 4, 5, 6]
part_ids = [1, 1, 2, 2, 3, 4]
g.partition.explicit(4, element_tags=elem_tags,
partitions=part_ids)
Parameters
parent : _SessionBase
The owning instance.
Source code in src/apeGmsh/mesh/Partition.py
| def __init__(self, parent: _SessionBase) -> None:
self._parent = parent
|
auto
auto(n_parts: int) -> Partition
Partition the current mesh into n_parts sub-domains using
Gmsh's built-in partitioner (Metis when available).
Must be called after g.mesh.generation.generate().
Parameters
n_parts : number of partitions (≥ 1)
Returns
self — for chaining
Source code in src/apeGmsh/mesh/Partition.py
| def auto(self, n_parts: int) -> Partition:
"""
Partition the current mesh into *n_parts* sub-domains using
Gmsh's built-in partitioner (Metis when available).
Must be called after ``g.mesh.generation.generate()``.
Parameters
----------
n_parts : number of partitions (≥ 1)
Returns
-------
self — for chaining
"""
if n_parts < 1:
raise ValueError(f"auto(): n_parts must be ≥ 1, got {n_parts}")
gmsh.model.mesh.partition(n_parts)
self._log(f"auto(n_parts={n_parts})")
return self
|
explicit
explicit(n_parts: int, *, element_tags: list[int], partitions: list[int]) -> Partition
Partition the mesh with an explicit per-element assignment.
Parameters
n_parts : total number of partitions declared
element_tags : list of element tags to assign
partitions : parallel list of partition IDs (1-based) for each
element in element_tags
Returns
self — for chaining
Example
::
g.partition.explicit(
2,
element_tags=[1, 2, 3, 4],
partitions =[1, 1, 2, 2],
)
Source code in src/apeGmsh/mesh/Partition.py
| def explicit(
self,
n_parts : int,
*,
element_tags: list[int],
partitions : list[int],
) -> Partition:
"""
Partition the mesh with an explicit per-element assignment.
Parameters
----------
n_parts : total number of partitions declared
element_tags : list of element tags to assign
partitions : parallel list of partition IDs (1-based) for each
element in *element_tags*
Returns
-------
self — for chaining
Example
-------
::
g.partition.explicit(
2,
element_tags=[1, 2, 3, 4],
partitions =[1, 1, 2, 2],
)
"""
if len(element_tags) != len(partitions):
raise ValueError(
f"explicit(): len(element_tags)={len(element_tags)} != "
f"len(partitions)={len(partitions)}"
)
gmsh.model.mesh.partition(
n_parts,
elementTags=element_tags,
partitions=partitions,
)
self._log(
f"explicit(n_parts={n_parts}, "
f"n_elements={len(element_tags)})"
)
return self
|
unpartition
unpartition() -> Partition
Remove the partition structure and restore a monolithic mesh.
Returns
self — for chaining
Source code in src/apeGmsh/mesh/Partition.py
| def unpartition(self) -> Partition:
"""
Remove the partition structure and restore a monolithic mesh.
Returns
-------
self — for chaining
"""
gmsh.model.mesh.unpartition()
self._log("unpartition()")
return self
|
renumber
renumber(*, method: str = 'RCMK', element_tags: list[int] | None = None) -> Partition
Compute and apply a node renumbering (e.g. RCMK bandwidth
reduction) — commonly run after partitioning to improve solver
performance.
Parameters
method : renumbering algorithm passed to
gmsh.model.mesh.computeRenumbering.
Supported values: "RCMK" (bandwidth reduction),
"Hilbert" (space-filling curve, good cache
locality), "Metis" (graph-partitioner ordering).
"RCMK" is the most commonly used default.
element_tags : restrict to a subset of elements (None = all)
Returns
self — for chaining
Source code in src/apeGmsh/mesh/Partition.py
| def renumber(
self,
*,
method : str = "RCMK",
element_tags: list[int] | None = None,
) -> Partition:
"""
Compute and apply a node renumbering (e.g. RCMK bandwidth
reduction) — commonly run after partitioning to improve solver
performance.
Parameters
----------
method : renumbering algorithm passed to
``gmsh.model.mesh.computeRenumbering``.
Supported values: ``"RCMK"`` (bandwidth reduction),
``"Hilbert"`` (space-filling curve, good cache
locality), ``"Metis"`` (graph-partitioner ordering).
``"RCMK"`` is the most commonly used default.
element_tags : restrict to a subset of elements (``None`` = all)
Returns
-------
self — for chaining
"""
old_tags, new_tags = gmsh.model.mesh.computeRenumbering(
method=method,
elementTags=element_tags or [],
)
gmsh.model.mesh.renumberNodes(
oldTags=list(old_tags),
newTags=list(new_tags),
)
self._log(
f"renumber(method={method!r}, "
f"n_nodes={len(old_tags)})"
)
return self
|
n_partitions
Return the current number of partitions (0 if not partitioned).
Source code in src/apeGmsh/mesh/Partition.py
| def n_partitions(self) -> int:
"""Return the current number of partitions (0 if not partitioned)."""
return gmsh.model.getNumberOfPartitions()
|
get_partitions
get_partitions(dim: int, tag: Tag) -> list[int]
Return the partition IDs that contain a given model entity.
Parameters
dim : entity dimension
tag : entity tag
Returns
list[int] partition IDs (empty for non-partitioned entities)
Source code in src/apeGmsh/mesh/Partition.py
| def get_partitions(self, dim: int, tag: Tag) -> list[int]:
"""
Return the partition IDs that contain a given model entity.
Parameters
----------
dim : entity dimension
tag : entity tag
Returns
-------
list[int] partition IDs (empty for non-partitioned entities)
"""
return list(gmsh.model.getPartitions(dim, tag))
|
get_parent
get_parent(dim: int, tag: Tag) -> DimTag
Return the (dim, tag) of the parent entity of a partitioned
sub-entity.
Parameters
dim : dimension of the partitioned entity
tag : tag of the partitioned entity
Source code in src/apeGmsh/mesh/Partition.py
| def get_parent(self, dim: int, tag: Tag) -> DimTag:
"""
Return the ``(dim, tag)`` of the parent entity of a partitioned
sub-entity.
Parameters
----------
dim : dimension of the partitioned entity
tag : tag of the partitioned entity
"""
p_dim, p_tag = gmsh.model.getParent(dim, tag)
return (p_dim, p_tag)
|
entity_table
entity_table(dim: int = -1) -> pd.DataFrame
Build a DataFrame of all model entities and their partition
membership.
Parameters
dim : restrict to a single dimension (-1 = all)
Returns
pd.DataFrame with columns:
dim entity dimension
tag entity tag
partitions comma-separated partition IDs (empty string = unpartitioned)
parent_dim parent entity dimension (-1 if top-level)
parent_tag parent entity tag (-1 if top-level)
Source code in src/apeGmsh/mesh/Partition.py
| def entity_table(self, dim: int = -1) -> pd.DataFrame:
"""
Build a DataFrame of all model entities and their partition
membership.
Parameters
----------
dim : restrict to a single dimension (``-1`` = all)
Returns
-------
pd.DataFrame with columns:
``dim`` entity dimension
``tag`` entity tag
``partitions`` comma-separated partition IDs (empty string = unpartitioned)
``parent_dim`` parent entity dimension (``-1`` if top-level)
``parent_tag`` parent entity tag (``-1`` if top-level)
"""
rows: list[dict] = []
entities = (
gmsh.model.getEntities(dim=dim)
if dim != -1
else gmsh.model.getEntities()
)
for ent_dim, ent_tag in entities:
try:
parts = list(gmsh.model.getPartitions(ent_dim, ent_tag))
except Exception:
parts = []
try:
p_dim, p_tag = gmsh.model.getParent(ent_dim, ent_tag)
except Exception:
p_dim, p_tag = -1, -1
rows.append({
'dim' : ent_dim,
'tag' : ent_tag,
'partitions' : ", ".join(str(p) for p in parts),
'parent_dim' : p_dim,
'parent_tag' : p_tag,
})
if not rows:
return pd.DataFrame(
columns=['dim', 'tag', 'partitions', 'parent_dim', 'parent_tag']
)
return pd.DataFrame(rows).set_index(['dim', 'tag'])
|
summary
Return a concise text summary of the current partition state:
number of partitions and a per-dimension entity count.
Source code in src/apeGmsh/mesh/Partition.py
| def summary(self) -> str:
"""
Return a concise text summary of the current partition state:
number of partitions and a per-dimension entity count.
"""
n = self.n_partitions()
if n == 0:
return (
f"Partition(model={self._parent.name!r}): "
f"not partitioned"
)
lines = [
f"Partition(model={self._parent.name!r}): "
f"{n} partition(s)",
]
df = self.entity_table()
if not df.empty:
# count entities that belong to at least one partition
partitioned = df[df['partitions'] != '']
counts = (
partitioned
.reset_index()
.groupby('dim')
.size()
.rename(index={0:'points',1:'curves',2:'surfaces',3:'volumes'})
)
for dim_label, count in counts.items():
lines.append(f" {dim_label:10s}: {count} partitioned entities")
return "\n".join(lines)
|
save
save(path: Path | str, *, one_file_per_partition: bool = False, create_topology: bool = False, create_physicals: bool = True) -> Partition
Write the partitioned mesh to file(s).
Parameters
path : output file path or base name.
The format is inferred from the extension
(".msh" is the natural choice for
partitioned meshes).
one_file_per_partition : when True, Gmsh writes one file per
partition alongside the combined file.
The per-partition files are named
<stem>_<k><suffix> (e.g.
mesh_1.msh, mesh_2.msh …).
create_topology : pass to Mesh.PartitionCreateTopology
create_physicals : pass to Mesh.PartitionCreatePhysicals
(keep True so solvers can find BC tags)
Returns
self — for chaining
Source code in src/apeGmsh/mesh/Partition.py
| def save(
self,
path : Path | str,
*,
one_file_per_partition: bool = False,
create_topology : bool = False,
create_physicals : bool = True,
) -> Partition:
"""
Write the partitioned mesh to file(s).
Parameters
----------
path : output file path or base name.
The format is inferred from the extension
(``".msh"`` is the natural choice for
partitioned meshes).
one_file_per_partition : when ``True``, Gmsh writes one file per
partition alongside the combined file.
The per-partition files are named
``<stem>_<k><suffix>`` (e.g.
``mesh_1.msh``, ``mesh_2.msh`` …).
create_topology : pass to ``Mesh.PartitionCreateTopology``
create_physicals : pass to ``Mesh.PartitionCreatePhysicals``
(keep ``True`` so solvers can find BC tags)
Returns
-------
self — for chaining
"""
path = Path(path)
gmsh.option.setNumber(
"Mesh.PartitionCreateTopology", int(create_topology)
)
gmsh.option.setNumber(
"Mesh.PartitionCreatePhysicals", int(create_physicals)
)
gmsh.option.setNumber(
"Mesh.PartitionSplitMeshFiles", int(one_file_per_partition)
)
gmsh.write(str(path))
self._log(
f"save({path}, "
f"one_file_per_partition={one_file_per_partition})"
)
return self
|
apeGmsh.mesh.View.View
View(parent: SessionProtocol)
Bases: _HasLogging
Solver-agnostic post-processing view composite attached to a apeGmsh
instance as g.view.
Wraps gmsh.view to inject scalar and vector fields onto the active
mesh. No solver dependency — you compute the result arrays yourself
and pass them in.
Usage::
# Element-wise scalar (constant per element)
g.view.add_element_scalar("VonMises", elem_tags, values)
# Nodal scalar (smooth contour via Gmsh interpolation)
g.view.add_node_scalar("sigma_xx avg", node_tags, values)
# Nodal vector (displacement arrows / deformed shape)
g.view.add_node_vector("Displacement", node_tags, vectors)
All add_* methods return the Gmsh view tag (int).
Parameters
parent : _SessionBase
Owning instance — used for name and _verbose.
Source code in src/apeGmsh/mesh/View.py
| def __init__(self, parent: _SessionBase) -> None:
self._parent = parent
self._views: dict[Tag, str] = {} # view_tag -> name
|
add_element_scalar
add_element_scalar(name: str, elem_tags: list[int] | ndarray, values: list[float] | ndarray, *, step: int = 0, time: float = 0.0) -> Tag
Add a scalar field with one value per element.
Parameters
name : view name shown in the Gmsh GUI sidebar
elem_tags : Gmsh element tags (from g.mesh.get_elements)
values : one scalar per element, same order as elem_tags
Returns
int Gmsh view tag
Source code in src/apeGmsh/mesh/View.py
| def add_element_scalar(
self,
name : str,
elem_tags : list[int] | ndarray,
values : list[float] | ndarray,
*,
step : int = 0,
time : float = 0.0,
) -> Tag:
"""
Add a scalar field with one value per element.
Parameters
----------
name : view name shown in the Gmsh GUI sidebar
elem_tags : Gmsh element tags (from ``g.mesh.get_elements``)
values : one scalar per element, same order as *elem_tags*
Returns
-------
int Gmsh view tag
"""
tags = [int(t) for t in elem_tags]
data = [[float(v)] for v in values]
v = gmsh.view.add(name)
gmsh.view.addModelData(
v, step, self._parent.name, "ElementData",
tags, data, time, 1,
)
gmsh.view.option.setNumber(v, "IntervalsType", 3) # continuous map
self._views[v] = name
self._log(f"add_element_scalar({name!r}) -> view {v} "
f"({len(tags)} elements)")
return v
|
add_element_vector
add_element_vector(name: str, elem_tags: list[int] | ndarray, vectors: ndarray, *, step: int = 0, time: float = 0.0) -> Tag
Add a vector field with one 3-component vector per element.
Parameters
vectors : shape (nElem, 3) — [vx, vy, vz] per element
Source code in src/apeGmsh/mesh/View.py
| def add_element_vector(
self,
name : str,
elem_tags : list[int] | ndarray,
vectors : ndarray,
*,
step : int = 0,
time : float = 0.0,
) -> Tag:
"""
Add a vector field with one 3-component vector per element.
Parameters
----------
vectors : shape ``(nElem, 3)`` — ``[vx, vy, vz]`` per element
"""
tags = [int(t) for t in elem_tags]
data = [[float(vectors[i, 0]), float(vectors[i, 1]), float(vectors[i, 2])]
for i in range(len(tags))]
v = gmsh.view.add(name)
gmsh.view.addModelData(
v, step, self._parent.name, "ElementData",
tags, data, time, 3,
)
self._views[v] = name
self._log(f"add_element_vector({name!r}) -> view {v}")
return v
|
add_node_scalar
add_node_scalar(name: str, node_tags: list[int] | ndarray, values: list[float] | ndarray, *, step: int = 0, time: float = 0.0) -> Tag
Add a scalar field with one value per node.
Parameters
node_tags : Gmsh node tags
values : one scalar per node, same order as node_tags
Source code in src/apeGmsh/mesh/View.py
| def add_node_scalar(
self,
name : str,
node_tags : list[int] | ndarray,
values : list[float] | ndarray,
*,
step : int = 0,
time : float = 0.0,
) -> Tag:
"""
Add a scalar field with one value per node.
Parameters
----------
node_tags : Gmsh node tags
values : one scalar per node, same order as *node_tags*
"""
tags = [int(t) for t in node_tags]
data = [[float(v)] for v in values]
v = gmsh.view.add(name)
gmsh.view.addModelData(
v, step, self._parent.name, "NodeData",
tags, data, time, 1,
)
gmsh.view.option.setNumber(v, "IntervalsType", 3)
self._views[v] = name
self._log(f"add_node_scalar({name!r}) -> view {v} ({len(tags)} nodes)")
return v
|
add_node_vector
add_node_vector(name: str, node_tags: list[int] | ndarray, vectors: ndarray, *, step: int = 0, time: float = 0.0, vector_type: int = 5) -> Tag
Add a vector field with one 3-component vector per node.
Parameters
vectors : shape (nNode, 2) or (nNode, 3) — missing components are zero-padded
vector_type : Gmsh display style (1=arrows, 2=cones, 5=displacement)
Source code in src/apeGmsh/mesh/View.py
| def add_node_vector(
self,
name : str,
node_tags : list[int] | ndarray,
vectors : ndarray,
*,
step : int = 0,
time : float = 0.0,
vector_type: int = 5,
) -> Tag:
"""
Add a vector field with one 3-component vector per node.
Parameters
----------
vectors : shape ``(nNode, 2)`` or ``(nNode, 3)`` — missing components are zero-padded
vector_type : Gmsh display style (1=arrows, 2=cones, 5=displacement)
"""
vecs = np.asarray(vectors)
if vecs.ndim == 1:
vecs = vecs.reshape(-1, 1)
ncols = vecs.shape[1]
if ncols < 3:
vecs = np.pad(vecs, ((0, 0), (0, 3 - ncols)))
tags = [int(t) for t in node_tags]
data = [[float(vecs[i, 0]), float(vecs[i, 1]), float(vecs[i, 2])]
for i in range(len(tags))]
v = gmsh.view.add(name)
gmsh.view.addModelData(
v, step, self._parent.name, "NodeData",
tags, data, time, 3,
)
gmsh.view.option.setNumber(v, "VectorType", vector_type)
self._views[v] = name
self._log(f"add_node_vector({name!r}) -> view {v} ({len(tags)} nodes)")
return v
|
list_views
list_views() -> dict[Tag, str]
Return {tag: name} for all views created through this class.
Source code in src/apeGmsh/mesh/View.py
| def list_views(self) -> dict[Tag, str]:
"""Return ``{tag: name}`` for all views created through this class."""
return dict(self._views)
|
count
Number of views created.
Source code in src/apeGmsh/mesh/View.py
| def count(self) -> int:
"""Number of views created."""
return len(self._views)
|
Algorithms & enums
apeGmsh.mesh._mesh_algorithms
Algorithm / optimise constants + _normalize_algorithm helper.
Split out of the old monolithic Mesh.py so the generation
sub-composite can import the normaliser without pulling in the
main :class:~apeGmsh.mesh.Mesh.Mesh module.
The public names here are re-exported from apeGmsh.mesh.Mesh and
from the top-level apeGmsh package for backwards-compatible
imports.
Algorithm2D
Bases: IntEnum
2-D meshing algorithm selector (legacy IntEnum form).
Prefer passing a string name to
:meth:apeGmsh.mesh.Mesh._Generation.set_algorithm — see
:data:ALGORITHM_2D and :class:MeshAlgorithm2D for the canonical
names and the accepted aliases.
Algorithm3D
Bases: IntEnum
3-D meshing algorithm selector (legacy IntEnum form).
MeshAlgorithm2D
Canonical 2-D algorithm names as string constants (IDE autocomplete).
MeshAlgorithm3D
Canonical 3-D algorithm names as string constants (IDE autocomplete).
OptimizeMethod
Mesh optimisation method names — use with g.mesh.generation.optimize.
apeGmsh.mesh._mesh_partitioning.RenumberResult
RenumberResult(method: str, n_nodes: int, n_elements: int, bandwidth_before: int, bandwidth_after: int)
Result of a mesh renumbering operation.
Attributes
method : str
Algorithm used ("simple", "rcm", "hilbert", "metis").
n_nodes : int
Number of nodes renumbered.
n_elements : int
Number of elements renumbered.
bandwidth_before : int
Semi-bandwidth before renumbering.
bandwidth_after : int
Semi-bandwidth after renumbering.
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def __init__(
self,
method: str,
n_nodes: int,
n_elements: int,
bandwidth_before: int,
bandwidth_after: int,
) -> None:
self.method = method
self.n_nodes = n_nodes
self.n_elements = n_elements
self.bandwidth_before = bandwidth_before
self.bandwidth_after = bandwidth_after
|
apeGmsh.mesh._mesh_partitioning.PartitionInfo
PartitionInfo(n_parts: int, elements_per_partition: dict[int, int])
Result of a mesh partitioning operation.
Attributes
n_parts : int
Number of partitions created.
elements_per_partition : dict[int, int]
{partition_id: element_count}.
Source code in src/apeGmsh/mesh/_mesh_partitioning.py
| def __init__(
self,
n_parts: int,
elements_per_partition: dict[int, int],
) -> None:
self.n_parts = n_parts
self.elements_per_partition = elements_per_partition
|
Group sets
apeGmsh.mesh._group_set.PhysicalGroupSet
PhysicalGroupSet(groups: dict[tuple[int, int], dict])
Bases: NamedGroupSet
Snapshot of solver-facing physical groups.
Accessed via fem.nodes.physical / fem.elements.physical
(shared reference) and indirectly via
fem.nodes.select(pg="Base").
Source code in src/apeGmsh/mesh/_group_set.py
| def __init__(self, groups: dict[tuple[int, int], dict]) -> None:
# Apply dtype coercion once at construction time
self._groups: dict[tuple[int, int], dict] = {}
for key, info in groups.items():
coerced = dict(info)
coerced['node_ids'] = _to_object(info['node_ids'])
coerced['node_coords'] = np.asarray(
info['node_coords'], dtype=np.float64)
if 'element_ids' in info:
coerced['element_ids'] = _to_object(info['element_ids'])
# Per-type groups (new extraction format)
if 'groups' in info:
coerced['groups'] = info['groups']
# Legacy flat connectivity (keep if present)
if 'connectivity' in info:
coerced['connectivity'] = _to_object(info['connectivity'])
self._groups[key] = coerced
self._name_index: dict[str, list[tuple[int, int]]] | None = None
# Cache for merged multi-dim info dicts
self._merged_cache: dict[str, dict] = {}
|
apeGmsh.mesh._group_set.LabelSet
LabelSet(groups: dict[tuple[int, int], dict])
Bases: NamedGroupSet
Snapshot of geometry-time labels (Tier 1).
Accessed via fem.nodes.labels / fem.elements.labels
(shared reference) and indirectly via
fem.nodes.select(label="col.web").
Source code in src/apeGmsh/mesh/_group_set.py
| def __init__(self, groups: dict[tuple[int, int], dict]) -> None:
# Apply dtype coercion once at construction time
self._groups: dict[tuple[int, int], dict] = {}
for key, info in groups.items():
coerced = dict(info)
coerced['node_ids'] = _to_object(info['node_ids'])
coerced['node_coords'] = np.asarray(
info['node_coords'], dtype=np.float64)
if 'element_ids' in info:
coerced['element_ids'] = _to_object(info['element_ids'])
# Per-type groups (new extraction format)
if 'groups' in info:
coerced['groups'] = info['groups']
# Legacy flat connectivity (keep if present)
if 'connectivity' in info:
coerced['connectivity'] = _to_object(info['connectivity'])
self._groups[key] = coerced
self._name_index: dict[str, list[tuple[int, int]]] | None = None
# Cache for merged multi-dim info dicts
self._merged_cache: dict[str, dict] = {}
|