FEM Broker — FEMData¶
Solver-agnostic snapshot returned by g.mesh.queries.get_fem_data(dim).
Native persistence¶
FEMData round-trips to a native model.h5 (the neutral zone)
without any solver in the loop:
fem = g.mesh.queries.get_fem_data(dim=3)
fem.to_h5("model.h5", model_name="Tower")
restored = FEMData.from_h5("model.h5") # integrity-checked load
to_h5(path, *, model_name="", apegmsh_version="", ndf=0) writes
the snapshot; from_h5(path, *, root="/") reads it back. root=
selects a non-root group when the neutral zone lives nested inside
a larger file (e.g. the two-zone canonical file written by the
bridge). The read is integrity-checked: a snapshot_id
mismatch between the written and re-derived content raises
MalformedH5Error.
The neutral zone is written at schema NEUTRAL_SCHEMA_VERSION
("2.10.0", defined in mesh/_femdata_h5_io.py); the OpenSees
zone written by the bridge carries its own SCHEMA_VERSION
("2.12.0"). Readers honour a two-version compatibility window
(ADR 0023).
This is the same neutral zone the session writes via
apeGmsh(save_to=...) / g.save() — see the
Session page. FEMData.from_h5
is also the entry point apeGmsh.from_h5 and g.compose build on
for chain-phase reassembly.
apeGmsh.mesh.FEMData ¶
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 ¶
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
summary ¶
One-line summary string.
Source code in src/apeGmsh/mesh/FEMData.py
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, ndf: ndarray | None = None, module_label: ndarray | None = None, provenance: ndarray | 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
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | |
module_label
property
¶
Per-node compose labels aligned 1:1 with :attr:ids.
Object ndarray of source-module labels (empty string for
host-owned rows), populated by the g.compose merge engine
(ADR 0038 §"Schema"). None when the broker carries no
module-label metadata (the uncomposed case). Read-only view
of the underlying array; consumers must not mutate it.
provenance
property
¶
Per-node provenance aligned 1:1 with :attr:ids (ADR 0049).
int8 array where PROVENANCE_MESH (0) marks an ordinary
Gmsh-vertex node and PROVENANCE_DECOUPLED (1) marks an
auxiliary node declared via g.decouple_node(...). None
when the broker carries no decoupled nodes (the common case).
Read-only view; consumers must not mutate it.
decoupled_ids
property
¶
Node IDs whose provenance is decoupled (ADR 0049).
Empty ndarray when the broker carries no decoupled nodes.
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)
Select a subset of nodes from this FEM snapshot.
Returns a :class:~apeGmsh.mesh._mesh_selection.MeshSelection
(point family — .in_box tests node coordinates) that
chains spatial-refinement verbs and terminates at .ids /
.coords / .result()::
# seed by PG, read bulk arrays directly
base = fem.nodes.select(pg="Base")
base.ids # list[int]
base.coords # ndarray (N, 3)
# chain verbs, then drive a loop or feed ops
for nid, xyz in fem.nodes.select(pg="Base").result():
ops.node(nid, *xyz)
# spatial narrowing
corner = (fem.nodes.select(pg="Body")
.in_box((0, 0, 0), (1, 1, 1))
.on_plane((0, 0, 0), (0, 0, 1), tol=1e-6))
# set algebra
all_bcs = (fem.nodes.select(pg="Base")
| fem.nodes.select(pg="Wall"))
No arguments seeds every domain node.
.. note::
Point family — .in_box tests node coordinates
against a half-open box [lo, hi) (pass
inclusive=True for the closed box). For
geometry-level selection use :meth:g.model.select.
Parameters¶
target :
Label name, physical group name, part name,
(dim, tag) pair, raw int tag, or a list thereof.
A string resolves through label → PG → part name in
that order.
pg :
Physical group name or list of names.
label :
Geometry-time label name or list. Labels survive
boolean operations.
tag :
Raw physical group tag (int or list).
partition :
Restrict to nodes that belong to this partition number.
dim :
Restrict to nodes on entities of this topological
dimension (0=point, 1=curve, 2=surface, 3=volume).
ids :
Explicit node id list. When given, all other
selectors are ignored.
Refining verbs¶
Each returns a new MeshSelection and composes freely:
.in_box(lo, hi, *, inclusive=False)— half-open[lo, hi)by default;inclusive=Truefor[lo, hi]..in_sphere(center, radius).on_plane(point, normal, *, tol)—tol=is required; raisesTypeErrorif omitted..nearest_to(point, *, count=1).where(predicate)— callablexyz → bool.|&-^(set algebra).
Terminals¶
.ids—list[int]of selected node IDs..coords—ndarray (N, 3)of coordinates..result()→ :class:~apeGmsh._kernel.payloads.NodeResult; iterate as(nid, xyz)pairs, read.ids/.coordsarrays, or call.to_dataframe().
Source code in src/apeGmsh/mesh/FEMData.py
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 | |
index ¶
Array index for a node ID. O(1) after first call.
Source code in src/apeGmsh/mesh/FEMData.py
ndf_for ¶
Return the per-node ndf (DOF count) for nid, if stored.
Per-node ndf is inferred by the apeSees bridge from
the incident element classes (ADR 0048); an element-less
decoupled node states it explicitly with ops.ndf(handle,
ndf=K) (ADR 0049). This accessor only returns a value when
such metadata was attached to the snapshot; the broker does not
infer ndf on its own.
Raises¶
KeyError
If nid is not a known node ID.
LookupError
If nid exists but the snapshot carries no ndf for it —
the bridge infers it from elements (or, for an element-less
node, ops.ndf(handle, ndf=K)).
Source code in src/apeGmsh/mesh/FEMData.py
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, module_label: dict[int, ndarray] | None = None, reinforce_ties=None, embed_ties=None, contacts=None, contact_planes=None, rebar_elements=None, interfaces=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
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 | |
connectivity
property
¶
Flat connectivity — only if all elements are the same type.
Raises¶
TypeError If multiple element types are present.
module_label
property
¶
Per-element compose labels keyed by element-type code.
Each value is an object ndarray aligned 1:1 with that type's
group ids (empty string for host-owned rows), populated by
the g.compose merge engine (ADR 0038 §"Schema"). None
when the broker carries no module-label metadata (the
uncomposed case). Read-only view; consumers must not mutate.
module_label_by_id ¶
Flat element-id -> compose label map across all types.
Concatenates the per-type :attr:module_label arrays against
each type's ids. None when no module-label metadata is
carried (the uncomposed case). Used by the split-emit path to
bucket each element into its owning module fragment.
Source code in src/apeGmsh/mesh/FEMData.py
type_table ¶
DataFrame of element types in the mesh.
Source code in src/apeGmsh/mesh/FEMData.py
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)
Select a subset of elements from this FEM snapshot.
Returns a :class:~apeGmsh.mesh._mesh_selection.MeshSelection
(point family — spatial verbs test element centroids) that
chains spatial-refinement verbs and terminates at .ids /
.connectivity / .result()::
# seed by PG, read element ids
body = fem.elements.select(pg="Body")
body.ids # list[int]
body.connectivity # ndarray (N, npe) — homogeneous mesh only
# filter to one element type in a spatial region
tets = (fem.elements.select(pg="Body", element_type="tet4")
.in_box((0, 0, 0), (5, 5, 3)))
# mixed mesh — call .resolve() on the GroupResult
gr = fem.elements.select(label="col.web").result()
ids, conn = gr.resolve() # single type
ids, conn = gr.resolve(element_type="hex8") # pick from mixed
No arguments seeds every element.
.. note::
Point family — .in_box tests element centroids
against a half-open box [lo, hi) (pass
inclusive=True for the closed box). For
geometry-level selection use :meth:g.model.select.
Parameters¶
target :
Label name, physical group name, part name,
(dim, tag) pair, raw int tag, or a list thereof.
A string resolves through label → PG → part name in
that order.
pg :
Physical group name or list of names.
label :
Geometry-time label name or list. Labels survive
boolean operations.
tag :
Raw physical group tag (int or list).
dim :
Restrict to elements of this topological dimension
(1=line, 2=surface, 3=volume).
element_type :
Restrict to a specific element type by name (e.g.
"tet4", "hex8") or Gmsh type code (int).
partition :
Restrict to elements belonging to this partition
number.
ids :
Explicit element id list. When given, all other
selectors are ignored.
Refining verbs¶
Each returns a new MeshSelection and composes freely
(spatial verbs test element centroids):
.in_box(lo, hi, *, inclusive=False)— half-open[lo, hi)by default;inclusive=Truefor[lo, hi]..in_sphere(center, radius).on_plane(point, normal, *, tol)—tol=is required; raisesTypeErrorif omitted..nearest_to(point, *, count=1).where(predicate)— callablecentroid_xyz → bool.|&-^(set algebra).
Terminals¶
.ids—list[int]of selected element IDs..coords—ndarray (N, 3)of element centroids..connectivity—ndarrayconnectivity; homogeneous selections only (raisesTypeErroron mixed element types)..groups()/.result()→ :class:~apeGmsh._kernel.payloads.GroupResult. Call.resolve()on theGroupResultto get(ids, connectivity); passelement_type=to pick one type from a mixed selection.
Source code in src/apeGmsh/mesh/FEMData.py
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 | |
index ¶
Array index for an element ID. O(1) after first call.
Source code in src/apeGmsh/mesh/FEMData.py
InspectComposite ¶
Introspection and summary methods.
Accessed via fem.inspect.
Source code in src/apeGmsh/mesh/FEMData.py
summary ¶
One-line mesh summary plus sub-composite counts.
Source code in src/apeGmsh/mesh/FEMData.py
node_table ¶
DataFrame of all nodes.
Source code in src/apeGmsh/mesh/FEMData.py
element_table ¶
DataFrame of all elements with a type column.
Source code in src/apeGmsh/mesh/FEMData.py
constraint_summary ¶
Human-readable breakdown of all constraints.
Source code in src/apeGmsh/mesh/FEMData.py
load_summary ¶
Human-readable breakdown of all loads.
Source code in src/apeGmsh/mesh/FEMData.py
mass_summary ¶
Human-readable breakdown of masses.
Source code in src/apeGmsh/mesh/FEMData.py
find_coincident_node_pairs ¶
find_coincident_node_pairs(*, tol: float = 1e-06, pg: str | None = None) -> dict[tuple[int, int], list[str]]
Find distinct nodes that share an XYZ within tolerance.
Opt-in diagnostic for suspect topology — most commonly the arc-line junction case, where OCC builds an arc-bounded wire without welding the arc endpoints onto the joining line's point tags. The mesh then carries two distinct nodes at every junction with no element or constraint bridging them.
Returns a dict mapping each coincident pair
(tag_a, tag_b) — sorted so tag_a < tag_b — to a list
of references that touch the pair:
"element <type>#<eid>"— both nodes appear in the same element's connectivity (legitimate forzeroLength, tied interfaces, etc.)"constraint <kind>"— equalDOF / rigidLink / diaphragm / kinematic / node_to_surface bridges the pair
An empty list is the smoking gun: the pair is coincident
but nothing references them together — i.e. an unbridged
duplicate (the cimbra arc-line corner). An entry with only
constraint refs means the user has explicitly tied the
pair; an entry with an element zeroLength* ref is the
canonical legitimate case.
Parameters¶
tol : float
Maximum Euclidean distance between two nodes to consider
them coincident. Default 1e-6. Single value — if
you need per-constraint-type tolerances (each constraint
kind has its own physical tol), drive the resolver's
preflight directly instead.
pg : str | None
If given, restrict the scan to nodes belonging to this
physical group. Otherwise scan all domain nodes.
Returns¶
dict[tuple[int, int], list[str]]
{(tag_a, tag_b): [ref, ...]} for every coincident
pair. Empty dict if no coincident pairs are found.
Warnings¶
Builds a full-model KDTree on each call (SciPy cKDTree,
with a NumPy O(N²) fallback if SciPy is unavailable). Avoid
invoking inside tight loops on million-node models — cache
the result yourself if you need it repeatedly.
Examples¶
::
pairs = fem.inspect.find_coincident_node_pairs(tol=1e-6)
for (a, b), refs in pairs.items():
if not refs:
print(f"UNBRIDGED coincident pair: {a}, {b}")
else:
print(f"Pair {a},{b} bridged by: {refs}")
Source code in src/apeGmsh/mesh/FEMData.py
1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 | |
FEMData ¶
FEMData(nodes: NodeComposite, elements: ElementComposite, info: MeshInfo, mesh_selection: 'MeshSelectionStore | None' = None, composed_from: 'ComposeSet | tuple[ComposeRecord, ...] | 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
1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 | |
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
¶
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
from_msh
classmethod
¶
Load FEMData from an external .msh file.
Source code in src/apeGmsh/mesh/FEMData.py
from_h5
classmethod
¶
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.
Parameters¶
path : str
Path to a model.h5 written by :meth:to_h5, g.save(),
or apeSees(fem).h5(path).
root : str, default "/"
Sub-group root inside path to read from. Default
rehydrates from the file root (standalone model.h5
shape). Per ADR 0020 (Phase 4 cleanup), composed
results.h5 files carry the same rich layout under
/model/; pass root="/model" to rehydrate from a
composed file. Backcompat: root="/" produces
byte-identical behaviour to the pre-refactor reader.
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
to_native_h5 ¶
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().
Phase 4 cleanup (ADR 0020): writes the rich neutral-zone
layout :func:write_fem_h5 produces at the file root, but
under group instead. The composed results.h5 thus
carries /model/meta, /model/nodes, /model/elements,
etc. — the same layout :func:read_fem_h5(path, root="/model")
rehydrates from. This eliminates the /opensees_archive/
zone that the previous lean embedding required to round-trip
the full :class:OpenSeesModel.
Source code in src/apeGmsh/mesh/FEMData.py
to_h5 ¶
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
from_native_h5
classmethod
¶
Reconstruct a FEMData from its embedded /model/ group.
Phase 4 cleanup (ADR 0020): production writers (:meth:to_native_h5
via :class:NativeWriter) embed the rich neutral zone — full
constraints, loads, masses, mesh selections and partitions
round-trip alongside nodes/elements/PGs. snapshot_id of
the rebuilt FEM matches the source's /meta/snapshot_id
attribute (the linking contract :class:Results.bind relies
on).
Source code in src/apeGmsh/mesh/FEMData.py
from_mpco_model
classmethod
¶
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
from_ladruno_model
classmethod
¶
Synthesize a partial FEMData from a .ladruno MODEL/ group.
Sibling of :meth:from_mpco_model for the self-describing
.ladruno layout (element groups carry a CONNECTIVITY
dataset + BASIS attrs). Carries nodes, elements (per OpenSees
class tag), and physical groups from MODEL/SETS. Missing vs.
native: apeGmsh labels, pre-mesh declarations, selection-set
names. snapshot_id will not match a native FEMData of the
same mesh — expected.
Source code in src/apeGmsh/mesh/FEMData.py
with_constraint ¶
Return a new :class:FEMData with record appended.
Pure transform. self is unchanged. Dispatch is by record
type:
===================================== =================================
Record subclass Appended to
===================================== =================================
NodePairRecord nodes.constraints
NodeGroupRecord nodes.constraints
NodeToSurfaceRecord nodes.constraints
InterpolationRecord elements.constraints
SurfaceCouplingRecord elements.constraints
SPRecord nodes.sp
===================================== =================================
Routing an unknown record subclass raises TypeError — this
is a fail-loud contract because the compose engine needs every
record to land in a known broker bucket.
Source code in src/apeGmsh/mesh/FEMData.py
with_load ¶
Return a new :class:FEMData with record appended.
Pure transform. self is unchanged. Dispatch is by record
type:
===================================== =================================
Record subclass Appended to
===================================== =================================
NodalLoadRecord nodes.loads
ElementLoadRecord elements.loads
SPRecord nodes.sp
===================================== =================================
Source code in src/apeGmsh/mesh/FEMData.py
compose ¶
compose(source: 'str | Path', *, label: str, translate: tuple[float, float, float] = (0.0, 0.0, 0.0), rotate: tuple[float, float, float, float] | None = None, anchor: str | None = None, partition_rank: int | None = None, properties: 'dict | None' = None, compose_size_per_module: int | None = None, max_compose_depth: int | None = None) -> 'FEMData'
Return a new :class:FEMData extending this chain with a
composed module.
Pure transformation. self is unchanged. The returned
FEMData's composed_from chain is
self.composed_from + (new_record,) and every IMPORT-verdict
record from the source H5 surfaces on the result, namespaced
with label and offset into a non-overlapping tag window
per ADR 0038 §"Tag-offset scheme".
Geometry / mesh build-phase operations are not part of this
primitive — the merge runs entirely against the FEMData
broker; no live Gmsh state is touched. The compose API is
the canonical entry point for cross-session composition:
FEMData.from_h5(path).compose("module.h5", label="A").
Drift hazard: the returned FEMData is decoupled from any
live gmsh state on the producing session. Mutating the
session's mesh/PG/label/parts AFTER calling compose without
also re-extracting + re-applying the bundle drops the
composed module's records on the floor. The
:meth:apeGmsh.compose shim handles this via session-level
bundle-replay; if you call this primitive directly, replay
is your responsibility.
See :func:apeGmsh.mesh._compose.Compose.compose for the full
parameter contract; max_compose_depth is the only
Compose.compose parameter intentionally absent here
(depth checks come in Phase 3E.1).
Source code in src/apeGmsh/mesh/FEMData.py
2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 | |
compose_tree ¶
Derived nested-compose tree view of self.composed_from.
Reconstructs the nested-compose hierarchy from this FEMData's
flat composed_from chain (per PR #369's flat-graft
storage). Returns a tuple of root
:class:~apeGmsh.mesh._compose.ComposeTreeNode instances;
each root carries its :class:ComposeRecord plus any direct
children parsed from joined labels via the separator-
alternation rule (depth-1 ., depth-2 /, depth-3
., ...).
Empty tuple when self.composed_from is empty (an
uncomposed FEMData).
Source code in src/apeGmsh/mesh/FEMData.py
with_mass ¶
Return a new :class:FEMData with record appended to
nodes.masses.
Pure transform. self is unchanged. Only
:class:~apeGmsh._kernel.records._masses.MassRecord is
accepted; anything else raises TypeError.
Source code in src/apeGmsh/mesh/FEMData.py
assess ¶
Compile a v1 :class:~apeGmsh.assess.AssessmentReport (ADR 0094).
figures=True writes one undeformed mesh still via
:meth:render. Default is False.
Source code in src/apeGmsh/mesh/FEMData.py
render ¶
render(path: 'str | Path', *, camera: 'str | None' = None, window_size: tuple[int, int] = (1280, 720)) -> 'Path | None'
Write one undeformed mesh still (ADR 0094 S1).
VTK offscreen — no Qt window, no event loop. Returns the
written :class:~pathlib.Path, or None (and prints the
[skip viewer] notice) under APEGMSH_SKIP_VIEWER=1 or
with no GL.
camera= defaults to xy for a planar model, iso
otherwise (ADR 0094 Amendment 3); pass it explicitly to
override.
Source code in src/apeGmsh/mesh/FEMData.py
viewer ¶
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. For a
headless mesh still use :meth:render. For an interactive
mesh window, use g.mesh.viewer().