FEMData — Solver-ready FEM mesh broker.
The main output of apeGmsh's meshing pipeline. Organized by what the
engineer needs: Nodes and Elements — with selections, BCs,
loads, and masses as sub-composites.
Top-level composites::
fem.nodes → NodeComposite (IDs, coords, nodal loads, masses, node constraints)
fem.elements → ElementComposite (per-type element groups, surface constraints, element loads)
fem.info → MeshInfo (mesh statistics)
fem.inspect → InspectComposite (introspection and summaries)
Construction::
fem = FEMData.from_gmsh(dim=3, session=g, ndf=3)
fem = FEMData.from_gmsh(session=g) # all dims
fem = FEMData.from_msh("bridge.msh", dim=2)
fem = FEMData(nodes=..., elements=..., info=...) # direct
Usage::
# Domain nodes — MeshSelection iterates as (node_id, xyz) pairs
for nid, xyz in fem.nodes.select():
ops.node(nid, *xyz)
# Supports
for nid in fem.nodes.select(pg="Base").ids:
ops.fix(nid, 1, 1, 1)
# Elements (iterate by type)
for group in fem.elements:
for eid, conn in group:
ops.element(group.type_name, eid, *conn, mat_tag)
# Elements (resolve to flat arrays — single type; .resolve() on the
# GroupResult that .result() returns)
ids, conn = fem.elements.select(label="col.web").result().resolve()
# Constraints
K = fem.nodes.constraints.Kind
for c in fem.nodes.constraints.pairs():
if c.kind == K.RIGID_BEAM:
ops.rigidLink("beam", c.master_node, c.slave_node)
MeshInfo
MeshInfo(n_nodes: int, n_elems: int, bandwidth: int, types: list[ElementTypeInfo] | None = None)
Read-only summary of mesh statistics.
Accessed via fem.info.
Attributes
n_nodes : int
n_elems : int
bandwidth : int
types : list[ElementTypeInfo]
Element types present in the mesh.
Source code in src/apeGmsh/mesh/FEMData.py
| def __init__(
self,
n_nodes: int,
n_elems: int,
bandwidth: int,
types: list[ElementTypeInfo] | None = None,
) -> None:
self.n_nodes = n_nodes
self.n_elems = n_elems
self.bandwidth = bandwidth
self.types = types or []
|
nodes_per_elem
property
First type's npe, or 0 if empty.
elem_type_name
property
First type's name, or empty string.
summary
One-line summary string.
Source code in src/apeGmsh/mesh/FEMData.py
| def summary(self) -> str:
"""One-line summary string."""
s = f"{self.n_nodes} nodes, {self.n_elems} elements"
if self.types:
type_parts = [f"{t.name}:{t.count}" for t in self.types]
s += f" ({', '.join(type_parts)})"
s += f", bandwidth={self.bandwidth}"
return s
|
NodeComposite
NodeComposite(node_ids: ndarray, node_coords: ndarray, physical: PhysicalGroupSet, labels: LabelSet, constraints=None, loads=None, sp=None, masses=None, partitions: dict[int, dict] | None = None, part_node_map: dict | None = None)
Access and query nodes from the FEM mesh.
Primary interface::
fem.nodes.select(pg="Base") → MeshSelection (iterates (id, xyz));
.result() → NodeResult, .ids, .coords
fem.nodes.select() → all domain nodes
Sub-composites::
fem.nodes.constraints → NodeConstraintSet
fem.nodes.loads → NodalLoadSet
fem.nodes.masses → MassSet
Public properties for raw array access::
fem.nodes.ids → ndarray(N,) object dtype
fem.nodes.coords → ndarray(N, 3) float64
Source code in src/apeGmsh/mesh/FEMData.py
| def __init__(
self,
node_ids: ndarray,
node_coords: ndarray,
physical: PhysicalGroupSet,
labels: LabelSet,
constraints=None,
loads=None,
sp=None,
masses=None,
partitions: dict[int, dict] | None = None,
part_node_map: dict | None = None,
) -> None:
self._ids = _to_object(node_ids)
self._coords = np.asarray(node_coords, dtype=np.float64)
self.physical = physical
self.labels = labels
self.constraints = NodeConstraintSet(constraints)
self.loads = NodalLoadSet(loads)
self.sp = SPSet(sp)
self.masses = MassSet(masses)
self._partitions: dict[int, dict] = partitions or {}
self._id_to_idx: dict[int, int] | None = None
# Snapshot of ``g.parts.build_node_map(...)`` at FEM-build
# time — lets ``get(target=part_label)`` resolve without
# needing a live Gmsh session (parts registry may be gone
# by the time the user queries). Dict of ``str -> set[int]``.
self._part_node_map: dict[str, set[int]] = part_node_map or {}
|
ids
property
All domain node IDs. ndarray(N,) object dtype.
coords
property
All domain node coordinates. ndarray(N, 3) float64.
partitions
property
Sorted list of partition IDs (empty if not partitioned).
select
select(target=None, *, pg=None, label=None, tag=None, partition: int | None = None, dim: int | None = None, ids=None)
Start a daisy-chainable node selection.
Returns a :class:~apeGmsh.mesh._mesh_selection.MeshSelection
(point family) that composes fluently and terminates with
.ids / .coords / .connectivity / .result() /
.resolve()::
fem.nodes.select(pg="Base").in_box(lo, hi).on_plane(p, n, tol=1e-6)
fem.nodes.select(ids=a) | fem.nodes.select(ids=b)
Accepts target / pg / label / tag /
partition / dim plus ids= (explicit id list); no-arg
seeds every domain node. Name resolution is not
re-implemented here: the
target/pg/label/tag/dim seed is obtained by
delegating verbatim to :meth:_resolve_nodes, preserving its
documented node-path KeyError-only swallow asymmetry (FP-4)
by reuse — and the optional partition filter reuses
:meth:_intersect_partition, so the resolved id set is exactly
what the locked resolution contract returns (no extra scoping
or boundary walk).
MeshSelection is imported deferred (mirrors
mesh/_mesh_structured.py): _mesh_selection imports only
the package-root leaf apeGmsh._kernel.chain at load, so this
adds no eager cross-package edge
(tests/test_import_dag_polarity.py stays green with the
baseline unchanged).
Source code in src/apeGmsh/mesh/FEMData.py
| def select(
self,
target=None,
*,
pg=None,
label=None,
tag=None,
partition: int | None = None,
dim: int | None = None,
ids=None,
):
"""Start a daisy-chainable node selection.
Returns a :class:`~apeGmsh.mesh._mesh_selection.MeshSelection`
(point family) that composes fluently and terminates with
``.ids`` / ``.coords`` / ``.connectivity`` / ``.result()`` /
``.resolve()``::
fem.nodes.select(pg="Base").in_box(lo, hi).on_plane(p, n, tol=1e-6)
fem.nodes.select(ids=a) | fem.nodes.select(ids=b)
Accepts ``target`` / ``pg`` / ``label`` / ``tag`` /
``partition`` / ``dim`` plus ``ids=`` (explicit id list); no-arg
seeds every domain node. Name resolution is **not**
re-implemented here: the
``target``/``pg``/``label``/``tag``/``dim`` seed is obtained by
delegating verbatim to :meth:`_resolve_nodes`, preserving its
documented node-path ``KeyError``-only swallow asymmetry (FP-4)
by reuse — and the optional ``partition`` filter reuses
:meth:`_intersect_partition`, so the resolved id set is exactly
what the locked resolution contract returns (no extra scoping
or boundary walk).
``MeshSelection`` is imported **deferred** (mirrors
``mesh/_mesh_structured.py``): ``_mesh_selection`` imports only
the package-root leaf ``apeGmsh._kernel.chain`` at load, so this
adds no eager cross-package edge
(``tests/test_import_dag_polarity.py`` stays green with the
baseline unchanged).
"""
# selection-unification-v2: the host hook returns the v2
# terminal ``MeshSelection`` (the point-family chain==terminal).
# Same deferred-import idiom — ``_mesh_selection`` imports only
# the package-root leaf ``_kernel.chain`` at load, so no new
# eager cross-package edge (one declared downward BASELINE
# triple; tripwire stays green).
from ._mesh_selection import MeshSelection # deferred — plan §3
if ids is not None:
atoms = [int(n) for n in ids]
elif (
target is None
and pg is None
and label is None
and tag is None
and partition is None
and dim is None
):
atoms = [int(n) for n in self._ids]
else:
seed_ids, seed_coords = self._resolve_nodes(
target, pg=pg, label=label, tag=tag, dim=dim
)
if partition is not None:
seed_ids, seed_coords = self._intersect_partition(
seed_ids, seed_coords, partition
)
atoms = [int(n) for n in seed_ids]
return MeshSelection(atoms, _engine=self)
|
index
Array index for a node ID. O(1) after first call.
Source code in src/apeGmsh/mesh/FEMData.py
| def index(self, nid: int) -> int:
"""Array index for a node ID. O(1) after first call."""
if self._id_to_idx is None:
self._id_to_idx = {
int(n): i for i, n in enumerate(self._ids)}
try:
return self._id_to_idx[int(nid)]
except KeyError:
if len(self._ids) > 0:
msg = (f"Node ID {nid} not found. "
f"Valid range: {int(self._ids.min())}-"
f"{int(self._ids.max())} "
f"({len(self._ids)} nodes)")
else:
msg = f"Node ID {nid} not found (no nodes)"
raise KeyError(msg) from None
|
ElementComposite
ElementComposite(groups: dict[int, ElementGroup], physical: PhysicalGroupSet, labels: LabelSet, constraints=None, loads=None, partitions: dict[int, dict] | None = None, part_elem_map: dict | None = None)
Access and query elements from the FEM mesh.
Iterable — yields ElementGroup objects::
for group in fem.elements:
print(group.type_name, len(group))
Selection API::
result = fem.elements.select(label="col.web").result()
ids, conn = result.resolve() # single-type
ids, conn = result.resolve(element_type='tet4') # pick one
Sub-composites::
fem.elements.constraints → SurfaceConstraintSet
fem.elements.loads → ElementLoadSet
Source code in src/apeGmsh/mesh/FEMData.py
| def __init__(
self,
groups: dict[int, ElementGroup],
physical: PhysicalGroupSet,
labels: LabelSet,
constraints=None,
loads=None,
partitions: dict[int, dict] | None = None,
part_elem_map: dict | None = None,
) -> None:
self._groups: dict[int, ElementGroup] = dict(groups)
self.physical = physical
self.labels = labels
self.constraints = SurfaceConstraintSet(constraints)
self.loads = ElementLoadSet(loads)
self._partitions: dict[int, dict] = partitions or {}
# Lazy caches
self._cached_ids: ndarray | None = None
self._id_to_idx: dict[int, int] | None = None
# Snapshot of ``part_label -> set[element_id]`` built at
# FEM-build time. Lets ``get(target=part_label)`` resolve
# without a live Gmsh session.
self._part_elem_map: dict[str, set[int]] = part_elem_map or {}
|
ids
property
All element IDs concatenated. ndarray(E,) int64.
connectivity
property
Flat connectivity — only if all elements are the same type.
Raises
TypeError
If multiple element types are present.
types
property
types: list[ElementTypeInfo]
Element types present in the mesh.
partitions
property
Sorted list of partition IDs.
is_homogeneous
property
True if all elements are the same type.
type_table
type_table() -> 'pd.DataFrame'
DataFrame of element types in the mesh.
Source code in src/apeGmsh/mesh/FEMData.py
| def type_table(self) -> "pd.DataFrame":
"""DataFrame of element types in the mesh."""
import pandas as pd
rows = []
for g in self._groups.values():
t = g.element_type
rows.append({
'code': t.code,
'name': t.name,
'gmsh_name': t.gmsh_name,
'dim': t.dim,
'order': t.order,
'npe': t.npe,
'count': t.count,
})
return pd.DataFrame(rows)
|
select
select(target=None, *, pg=None, label=None, tag=None, dim: int | None = None, element_type: str | int | None = None, partition: int | None = None, ids=None)
Start a daisy-chainable element selection.
Returns a :class:~apeGmsh.mesh._mesh_selection.MeshSelection
(point family — atoms are element ids, spatial verbs operate on
element centroids) that composes fluently and terminates with
.ids / .coords / .connectivity / .groups() /
.result() / .resolve()::
fem.elements.select(pg="Body").in_box(lo, hi).on_plane(p, n, tol=1e-6)
fem.elements.select(ids=a) | fem.elements.select(ids=b)
Accepts target / pg / label / tag / dim /
element_type / partition plus ids= (explicit id
list); no-arg seeds every element. Name resolution is not
re-implemented here: the target/pg/label/tag
seed is obtained by delegating verbatim to
:meth:_resolve_elem_ids, preserving its documented
element-path (KeyError, ValueError) swallow (FP-4) by
reuse. The auxiliary dim/element_type/partition
filters reuse the shared :meth:_filtered_groups helper (no
filter logic re-implemented), so the resolved selection is
exactly what the locked resolution contract returns.
MeshSelection is imported deferred (mirrors
mesh/_mesh_structured.py): _mesh_selection imports only
the package-root leaf apeGmsh._kernel.chain at load, so this
adds no eager cross-package edge
(tests/test_import_dag_polarity.py stays green with the
baseline unchanged).
Source code in src/apeGmsh/mesh/FEMData.py
| def select(
self,
target=None,
*,
pg=None,
label=None,
tag=None,
dim: int | None = None,
element_type: str | int | None = None,
partition: int | None = None,
ids=None,
):
"""Start a daisy-chainable element selection.
Returns a :class:`~apeGmsh.mesh._mesh_selection.MeshSelection`
(point family — atoms are element ids, spatial verbs operate on
element centroids) that composes fluently and terminates with
``.ids`` / ``.coords`` / ``.connectivity`` / ``.groups()`` /
``.result()`` / ``.resolve()``::
fem.elements.select(pg="Body").in_box(lo, hi).on_plane(p, n, tol=1e-6)
fem.elements.select(ids=a) | fem.elements.select(ids=b)
Accepts ``target`` / ``pg`` / ``label`` / ``tag`` / ``dim`` /
``element_type`` / ``partition`` plus ``ids=`` (explicit id
list); no-arg seeds every element. Name resolution is **not**
re-implemented here: the ``target``/``pg``/``label``/``tag``
seed is obtained by delegating verbatim to
:meth:`_resolve_elem_ids`, preserving its documented
element-path ``(KeyError, ValueError)`` swallow (FP-4) by
reuse. The auxiliary ``dim``/``element_type``/``partition``
filters reuse the shared :meth:`_filtered_groups` helper (no
filter logic re-implemented), so the resolved selection is
exactly what the locked resolution contract returns.
``MeshSelection`` is imported **deferred** (mirrors
``mesh/_mesh_structured.py``): ``_mesh_selection`` imports only
the package-root leaf ``apeGmsh._kernel.chain`` at load, so this
adds no eager cross-package edge
(``tests/test_import_dag_polarity.py`` stays green with the
baseline unchanged).
"""
# selection-unification-v2: the host hook returns the v2
# terminal ``MeshSelection`` (the point-family chain==terminal).
# Same deferred-import idiom; no new eager cross-package edge.
from ._mesh_selection import MeshSelection # deferred — plan §3
if ids is not None:
atoms = [int(e) for e in ids]
elif (
target is None
and pg is None
and label is None
and tag is None
and dim is None
and element_type is None
and partition is None
):
atoms = [int(e) for e in self.ids]
elif dim is None and element_type is None and partition is None:
# Pure name/target seed — delegate to the exact resolver
# `.get()` uses (FP-4 element-path swallow preserved by
# reuse). `None` means "all" (no PG/label/tag/target).
id_set = self._resolve_elem_ids(
target, pg=pg, label=label, tag=tag
)
atoms = (
[int(e) for e in self.ids]
if id_set is None
else [int(e) for e in id_set]
)
else:
# Auxiliary dim/element_type/partition filter present —
# reuse the verbatim filter helper `_filtered_groups`
# (selection-unification v2 P3-R / §6.3 M-STOP-1: the exact
# body the now-removed public `get` used), so select(...)
# stays byte-identical to the pre-P3-R get(...) path.
atoms = [
int(e)
for e in self._filtered_groups(
target, pg=pg, label=label, tag=tag, dim=dim,
element_type=element_type, partition=partition,
).ids
]
return MeshSelection(atoms, _engine=self)
|
index
Array index for an element ID. O(1) after first call.
Source code in src/apeGmsh/mesh/FEMData.py
| def index(self, eid: int) -> int:
"""Array index for an element ID. O(1) after first call."""
if self._id_to_idx is None:
self._id_to_idx = {
int(e): i for i, e in enumerate(self.ids)}
try:
return self._id_to_idx[int(eid)]
except KeyError:
ids = self.ids
if len(ids) > 0:
msg = (f"Element ID {eid} not found. "
f"Valid range: {int(ids.min())}-"
f"{int(ids.max())} "
f"({len(ids)} elements)")
else:
msg = f"Element ID {eid} not found (no elements)"
raise KeyError(msg) from None
|
InspectComposite
InspectComposite(fem: 'FEMData')
Introspection and summary methods.
Accessed via fem.inspect.
Source code in src/apeGmsh/mesh/FEMData.py
| def __init__(self, fem: "FEMData") -> None:
self._fem = fem
|
summary
One-line mesh summary plus sub-composite counts.
Source code in src/apeGmsh/mesh/FEMData.py
| def summary(self) -> str:
"""One-line mesh summary plus sub-composite counts."""
f = self._fem
lines = [f.info.summary()]
# Physical groups
pg = f.nodes.physical
if pg:
lines.append(f" Physical groups ({len(pg)}):")
for (d, t), info in sorted(pg._groups.items()):
name = info.get('name', '')
n_n = len(info['node_ids'])
eids = info.get('element_ids')
n_e = len(eids) if eids is not None else 0
lbl = f'"{name}"' if name else f"tag={t}"
parts = f"{n_n} nodes"
if n_e:
parts += f", {n_e} elems"
lines.append(f" ({d}) {lbl:24s} {parts}")
# Labels
lb = f.nodes.labels
if lb:
lines.append(f" Labels ({len(lb)}):")
for (d, t), info in sorted(lb._groups.items()):
name = info.get('name', '')
n_n = len(info['node_ids'])
eids = info.get('element_ids')
n_e = len(eids) if eids is not None else 0
parts = f"{n_n} nodes"
if n_e:
parts += f", {n_e} elems"
lines.append(f" ({d}) {name!r:24s} {parts}")
# Element types
if f.info.types:
lines.append(f" Element types ({len(f.info.types)}):")
for etype in f.info.types:
lines.append(
f" {etype.name:12s} dim={etype.dim}, "
f"order={etype.order}, npe={etype.npe}, "
f"count={etype.count}")
# Constraints
nc = f.nodes.constraints
sc = f.elements.constraints
if nc:
lines.append(f" Node constraints: {nc!r}")
if sc:
lines.append(f" Surface constraints: {sc!r}")
if f.nodes.loads:
lines.append(f" Nodal loads: {f.nodes.loads!r}")
if f.elements.loads:
lines.append(f" Element loads: {f.elements.loads!r}")
if f.nodes.masses:
lines.append(f" {f.nodes.masses!r}")
return "\n".join(lines)
|
node_table
node_table() -> 'pd.DataFrame'
DataFrame of all nodes.
Source code in src/apeGmsh/mesh/FEMData.py
| def node_table(self) -> "pd.DataFrame":
"""DataFrame of all nodes."""
import pandas as pd
f = self._fem
return pd.DataFrame(
f.nodes.coords,
index=pd.Index(
[int(x) for x in f.nodes.ids], name='node_id'),
columns=['x', 'y', 'z'],
)
|
element_table
element_table() -> 'pd.DataFrame'
DataFrame of all elements with a type column.
Source code in src/apeGmsh/mesh/FEMData.py
| def element_table(self) -> "pd.DataFrame":
"""DataFrame of all elements with a ``type`` column."""
import pandas as pd
rows = []
for group in self._fem.elements:
for eid, conn_row in group:
row: dict = {'elem_id': eid, 'type': group.type_name}
for j, nid in enumerate(conn_row):
row[f'n{j}'] = int(nid)
rows.append(row)
return pd.DataFrame(rows).set_index('elem_id')
|
constraint_summary
constraint_summary() -> str
Human-readable breakdown of all constraints.
Source code in src/apeGmsh/mesh/FEMData.py
| def constraint_summary(self) -> str:
"""Human-readable breakdown of all constraints."""
f = self._fem
lines = []
def _kind_summary(record_set, header):
if not record_set:
return
lines.append(f"{header} ({len(record_set)} records):")
counts: dict[str, int] = {}
names: dict[str, str] = {}
for r in record_set:
k = r.kind
counts[k] = counts.get(k, 0) + 1
if k not in names and getattr(r, 'name', None):
names[k] = r.name
for k, count in sorted(counts.items()):
hint = f" (source: {names[k]!r})" if k in names else ""
lines.append(f" {k:24s} {count:>4d}{hint}")
_kind_summary(f.nodes.constraints, "Node constraints")
nc = f.nodes.constraints
if nc:
n_phantom = len(nc.phantom_nodes())
if n_phantom:
lines.append(
f" {'phantom nodes':24s} {n_phantom:>4d}"
f" (created by node_to_surface)")
_kind_summary(f.elements.constraints, "Surface constraints")
if not lines:
return "No constraints."
return "\n".join(lines)
|
load_summary
Human-readable breakdown of all loads.
Source code in src/apeGmsh/mesh/FEMData.py
| def load_summary(self) -> str:
"""Human-readable breakdown of all loads."""
f = self._fem
lines = []
nl = f.nodes.loads
if nl:
lines.append(f"Nodal loads ({len(nl)} records):")
for pat in nl.patterns():
recs = nl.by_pattern(pat)
name_hint = ""
for r in recs:
if getattr(r, 'name', None):
name_hint = f" (source: {r.name!r})"
break
lines.append(
f" Pattern {pat!r:16s} {len(recs):>4d} "
f"nodal{name_hint}")
el = f.elements.loads
if el:
lines.append(f"Element loads ({len(el)} records):")
for pat in el.patterns():
erecs = el.by_pattern(pat)
name_hint = ""
for er in erecs:
if getattr(er, 'name', None):
name_hint = f" (source: {er.name!r})"
break
ltype = getattr(erecs[0], 'load_type', 'element') if erecs else 'element'
lines.append(
f" Pattern {pat!r:16s} {len(erecs):>4d} "
f"{ltype}{name_hint}")
if not lines:
return "No loads."
return "\n".join(lines)
|
mass_summary
Human-readable breakdown of masses.
Source code in src/apeGmsh/mesh/FEMData.py
| def mass_summary(self) -> str:
"""Human-readable breakdown of masses."""
f = self._fem
ms = f.nodes.masses
if not ms:
return "No masses."
lines = [f"Nodal masses ({len(ms)} nodes):"]
lines.append(f" Total mass: {ms.total_mass():.6g}")
for r in ms:
if getattr(r, 'name', None):
lines.append(f" Source: {r.name!r}")
break
return "\n".join(lines)
|
FEMData
FEMData(nodes: NodeComposite, elements: ElementComposite, info: MeshInfo, mesh_selection: 'MeshSelectionStore | None' = None)
Solver-ready FEM mesh broker.
Organized by what the user needs::
fem.nodes → NodeComposite
fem.elements → ElementComposite
fem.info → MeshInfo
fem.inspect → InspectComposite
Source code in src/apeGmsh/mesh/FEMData.py
| def __init__(
self,
nodes: NodeComposite,
elements: ElementComposite,
info: MeshInfo,
mesh_selection: "MeshSelectionStore | None" = None,
) -> None:
self.nodes = nodes
self.elements = elements
self.info = info
self.mesh_selection = mesh_selection
self.inspect = InspectComposite(self)
# Wire the sibling NodeComposite onto the ElementComposite so
# fem.elements.select(...) can compute element centroids
# in-memory (no live Gmsh session needed — works for
# import-origin FEMData too). Every construction path funnels
# through this __init__, so one wiring line covers from_gmsh /
# from_msh / from_h5 / from_native / from_mpco / direct. The
# attribute name is the contract shared with
# mesh/_mesh_selection.NODES_REF_ATTR.
elements._apegmsh_nodes_ref = nodes
|
partitions
property
Sorted list of partition IDs.
snapshot_id
property
Deterministic content hash identifying this FEMData snapshot.
Computed once and cached. Used by the Results module to bind
result files to their producing geometry — see
internal_docs/Results_architecture.md § "FEMData embedding
& binding".
from_gmsh
classmethod
from_gmsh(dim: int | None = None, *, session=None, ndf: int = 6, remove_orphans: bool = False)
Extract FEMData from a live Gmsh session.
Parameters
dim : int or None
Element dimension to extract. None = all dims.
session : apeGmsh session, optional
When provided, auto-resolves constraints, loads, masses.
ndf : int
DOFs per node for load/mass vector padding.
remove_orphans : bool
If True, remove mesh nodes not connected to any element.
Source code in src/apeGmsh/mesh/FEMData.py
| @classmethod
def from_gmsh(
cls,
dim: int | None = None,
*,
session=None,
ndf: int = 6,
remove_orphans: bool = False,
):
"""Extract FEMData from a live Gmsh session.
Parameters
----------
dim : int or None
Element dimension to extract. ``None`` = all dims.
session : apeGmsh session, optional
When provided, auto-resolves constraints, loads, masses.
ndf : int
DOFs per node for load/mass vector padding.
remove_orphans : bool
If True, remove mesh nodes not connected to any element.
"""
from ._fem_factory import _from_gmsh
return _from_gmsh(
cls, dim=dim, session=session, ndf=ndf,
remove_orphans=remove_orphans)
|
from_msh
classmethod
from_msh(path: str, dim: int | None = 2, *, remove_orphans: bool = False)
Load FEMData from an external .msh file.
Source code in src/apeGmsh/mesh/FEMData.py
| @classmethod
def from_msh(
cls,
path: str,
dim: int | None = 2,
*,
remove_orphans: bool = False,
):
"""Load FEMData from an external ``.msh`` file."""
from ._fem_factory import _from_msh
return _from_msh(cls, path=path, dim=dim,
remove_orphans=remove_orphans)
|
from_h5
classmethod
from_h5(path: str) -> 'FEMData'
Load a :class:FEMData snapshot from a root-layout model.h5.
Inverse of :meth:to_h5. Reads the seven neutral-zone groups
plus /meta and rebuilds nodes, elements (per type),
physical groups, labels, mesh selections, constraints, loads,
and masses — everything the writer round-trips.
Use this to resume a session-saved model in a later script::
# script 1 — build & save
with apeGmsh(model_name="m", save_to="m.h5") as g:
...
# script 2 — analyse
fem = FEMData.from_h5("m.h5")
apeSees(fem).h5("m.h5") # enrich with /opensees/...
Source code in src/apeGmsh/mesh/FEMData.py
| @classmethod
def from_h5(cls, path: str) -> "FEMData":
"""Load a :class:`FEMData` snapshot from a root-layout ``model.h5``.
Inverse of :meth:`to_h5`. Reads the seven neutral-zone groups
plus ``/meta`` and rebuilds nodes, elements (per type),
physical groups, labels, mesh selections, constraints, loads,
and masses — everything the writer round-trips.
Use this to resume a session-saved model in a later script::
# script 1 — build & save
with apeGmsh(model_name="m", save_to="m.h5") as g:
...
# script 2 — analyse
fem = FEMData.from_h5("m.h5")
apeSees(fem).h5("m.h5") # enrich with /opensees/...
"""
from ._femdata_h5_io import read_fem_h5
return read_fem_h5(path)
|
to_native_h5
to_native_h5(group) -> None
Embed this FEMData into an open HDF5 group (/model/).
Used by NativeWriter to snapshot the geometry alongside
results. The reconstructed FEMData (via from_native_h5)
will produce the same snapshot_id — this is the linking
contract for Results.bind().
Source code in src/apeGmsh/mesh/FEMData.py
| def to_native_h5(self, group) -> None:
"""Embed this FEMData into an open HDF5 group (``/model/``).
Used by ``NativeWriter`` to snapshot the geometry alongside
results. The reconstructed FEMData (via ``from_native_h5``)
will produce the same ``snapshot_id`` — this is the linking
contract for ``Results.bind()``.
"""
from ._femdata_native_io import write_fem_to_h5
write_fem_to_h5(self, group)
|
to_h5
to_h5(path: str, *, model_name: str = '', apegmsh_version: str = '', ndf: int = 0) -> None
Write a fresh model.h5 containing the neutral zone.
Phase 8.5 entry point: dumps everything the broker knows about
the model (nodes, elements per type, physical groups, labels,
constraints, loads, masses) into a root-level
model.h5. No /opensees/ content is emitted — absent
enrichment is the right "no solver loaded" signal.
Use apeSees(fem).h5(path) instead to get a fully enriched
file (neutral zone + /opensees/...).
Source code in src/apeGmsh/mesh/FEMData.py
| def to_h5(
self,
path: str,
*,
model_name: str = "",
apegmsh_version: str = "",
ndf: int = 0,
) -> None:
"""Write a fresh ``model.h5`` containing the neutral zone.
Phase 8.5 entry point: dumps everything the broker knows about
the model (nodes, elements per type, physical groups, labels,
constraints, loads, masses) into a root-level
``model.h5``. No ``/opensees/`` content is emitted — absent
enrichment is the right "no solver loaded" signal.
Use ``apeSees(fem).h5(path)`` instead to get a fully enriched
file (neutral zone + ``/opensees/...``).
"""
from ._femdata_h5_io import write_fem_h5
write_fem_h5(
self, path,
model_name=model_name,
apegmsh_version=apegmsh_version,
ndf=ndf,
)
|
from_native_h5
classmethod
from_native_h5(group) -> 'FEMData'
Reconstruct a FEMData from its embedded /model/ group.
The reconstructed object carries nodes, elements (per type),
physical groups, and labels. Loads/masses/constraints are not
round-tripped (they don't affect snapshot_id and the
viewer doesn't need them).
Source code in src/apeGmsh/mesh/FEMData.py
| @classmethod
def from_native_h5(cls, group) -> "FEMData":
"""Reconstruct a FEMData from its embedded ``/model/`` group.
The reconstructed object carries nodes, elements (per type),
physical groups, and labels. Loads/masses/constraints are not
round-tripped (they don't affect ``snapshot_id`` and the
viewer doesn't need them).
"""
from ._femdata_native_io import read_fem_from_h5
return read_fem_from_h5(group)
|
from_mpco_model
classmethod
from_mpco_model(group) -> 'FEMData'
Synthesize a partial FEMData from an MPCO MODEL/ group.
Carries: nodes, elements (per OpenSees class tag), physical
groups derived from MPCO Regions (MODEL/SETS).
Missing vs. native:
- apeGmsh-specific labels
- Pre-mesh declarations (loads / masses / constraints)
- STKO named selection sets (those live in .cdata sidecars)
- Gmsh-style element type codes (uses negated class_tag instead)
snapshot_id will not match a native FEMData of the same
mesh — that's expected. Results.bind() will refuse such
mismatches.
Source code in src/apeGmsh/mesh/FEMData.py
| @classmethod
def from_mpco_model(cls, group) -> "FEMData":
"""Synthesize a partial FEMData from an MPCO ``MODEL/`` group.
Carries: nodes, elements (per OpenSees class tag), physical
groups derived from MPCO Regions (``MODEL/SETS``).
Missing vs. native:
- apeGmsh-specific ``labels``
- Pre-mesh declarations (loads / masses / constraints)
- STKO named selection sets (those live in ``.cdata`` sidecars)
- Gmsh-style element type codes (uses negated class_tag instead)
``snapshot_id`` will not match a native FEMData of the same
mesh — that's expected. ``Results.bind()`` will refuse such
mismatches.
"""
from ._femdata_mpco_io import read_fem_from_mpco
return read_fem_from_mpco(group)
|
viewer
viewer(*, blocking: bool = False) -> None
Open a non-interactive mesh viewer from this snapshot.
Currently disabled — the legacy Results.from_fem(...).viewer()
path was removed when the Results module was rebuilt. The new
flow is being designed as part of the viewer rebuild project;
see internal_docs/Results_architecture.md (Phase 9).
Source code in src/apeGmsh/mesh/FEMData.py
| def viewer(self, *, blocking: bool = False) -> None:
"""Open a non-interactive mesh viewer from this snapshot.
Currently disabled — the legacy ``Results.from_fem(...).viewer()``
path was removed when the Results module was rebuilt. The new
flow is being designed as part of the viewer rebuild project;
see ``internal_docs/Results_architecture.md`` (Phase 9).
"""
raise NotImplementedError(
"fem.viewer() relied on the legacy Results class which has "
"been rebuilt. The replacement is part of the viewer rebuild "
"project (Phase 9 in Results_architecture.md). For mesh-only "
"viewing in the meantime, use g.mesh.viewer() with no args."
)
|