Skip to content

Meshing

The meshing stage: typed gmsh settings, the mesh backends, element sizing, and the body and boundary groups a STEP is meshed into.

femforge.meshing

The meshing stage: typed gmsh settings, the mesh backends, element sizing, and the body and boundary groups a STEP is meshed into.

Modules:

  • backend –

    The mesh seam: what a meshing job carries and reports, and the backends that mesh it in this process or in a worker.

  • boundary –

    Boundary physical groups derived from body adjacency: the mesh surfaces (3D) or curves (2D) boundary conditions attach to without hand-labelling.

  • config –

    The gmsh mesh settings and the config surface the mesher reads.

  • firewall –

    Keeping gmsh out of a process: an import hook for an entry point that must never load it.

  • geometry_utils –

    Per-entity geometry read from gmsh: bounding box, center of mass and volume.

  • gmsh_backend –

    The in-process gmsh meshing behind GmshMesher and the mesh worker.

  • gmsh_options –

    Typed gmsh options, each field carrying its gmsh option name as Map(...) metadata.

  • groups –

    The gmsh-free mesh group records: the body groups a mesher builds, their log summaries, and the groups.json a cached mesh carries.

  • mesh_entity –

    Resolves the gmsh entities of an imported STEP against the domain's materials and region tags.

  • mesh_sizing –

    Mesh element sizing: the domain-wide size plus per-region overrides, decided without gmsh and applied through injectable setters.

  • mesher –

    The gmsh mesher: a STEP's bodies matched to materials and tags, grouped into physical groups, sized, and meshed into a .msh.

  • naming –

    The physical-group name grammar: the normalization and composition rules shared by the mesher (emit) and the bundle readers (require).

  • spec –

    The mesh artifact's spec: the plain data that decides the Elmer mesh files and their group names.

  • worker –

    The mesh worker: python -m femforge.meshing.worker JOB OUTCOME meshes one job with gmsh and writes how it ended.

Classes:

  • MeshConfig –

    How the geometry is discretized for the gmsh backend: the STEP source, domain-wide element size, and typed gmsh knobs.

  • SupportsMeshing –

    The config surface the gmsh Mesher requires, which SimulationConfig satisfies structurally.

  • GmshOptions –

    Typed gmsh meshing options; a knob left at None keeps gmsh's own default.

  • MeshAlgorithm2D –

    A gmsh 2D meshing algorithm, valued as the integer Mesh.Algorithm takes.

  • MeshAlgorithm3D –

    A gmsh 3D meshing algorithm, valued as the integer Mesh.Algorithm3D takes.

  • MeshBackend –

    Meshes a job into a gmsh .msh plus its group records; each backend decides where gmsh runs.

  • MeshJob –

    One meshing request as plain data: the mesh spec, the STEP to read, and the .msh to write.

  • MeshReport –

    What one meshing job left: the .msh, its group records, and the gmsh release that wrote it.

  • MeshError –

    Meshing failed: the STEP's bodies could not be grouped or meshed, or the mesh worker crashed or ran out of time.

  • MeshInputs –

    The config surface a mesher reads, rebuilt from a MeshJob alone.

  • GmshMesher –

    Meshes in this process with gmsh, which loads on the first job and is finalized after every one.

  • WorkerMesher –

    Meshes in a child Python process, so gmsh, its license and its crashes stay out of this one.

  • MeshSpec –

    Everything that decides the Elmer mesh files and their group names, and nothing that only decides the solve.

  • TagSelectorSpec –

    The grouping-relevant half of an EntityTag: its selector and per-region size, never its conditions.

  • PhysicalGroup –

    A single gmsh physical group plus the metadata to reconstruct the matching Elmer body; both sides share the same name and integer id.

  • MeshGroupsFile –

    groups.json: the group records and element count a mesh artifact carries beside ElmerGrid's files.

  • BodyGroupRecord –

    One body group of a cached mesh: what it takes to rebuild its PhysicalGroup against a config.

  • BoundaryGroupRecord –

    One boundary group of a cached mesh.

  • BoundaryGroup –

    A single gmsh boundary physical group derived from body adjacency — the one-dimension-down sibling of PhysicalGroup, targeted by Elmer as Target Boundaries.

  • BoundaryPartition –

    One planned boundary group: the output of the pure adjacency partition, before gmsh assigns it a physical-group id.

  • ResolvedRegionMeshSize –

    A resolved per-region target element size and the volume tags it applies to.

  • EntityGeometry –

    Axis-aligned bounding box, center of mass and volume of one gmsh entity.

Functions:

  • mesh_inputs –

    The mesher's inputs for job: the frame of its dimension, its mesh settings, and a domain of bare materials and tag selectors.

  • block_gmsh –

    Make every later import gmsh in this process raise ImportError, so an entry point can prove it never loads gmsh.

  • raise_on_duplicate_names –

    Raise when two body groups share a name, e.g. one Name authored on parts of two materials.

  • format_physical_groups –

    Multi-line material → tag-group → physical-group summary for one log record (embeds newlines).

  • format_boundary_groups –

    Multi-line boundary-group summary for one log record (embeds newlines).

  • partition_boundary_faces –

    Group every boundary face by the bodies it borders into sorted interface (A__B), external (A__EXT) and symmetry-axis (A__AXIS) partitions.

  • read_face_adjacency –

    Map each boundary entity of the body_dim body groups to the names of the bodies it bounds, read from a synchronized gmsh model.

  • axis_entity_tags –

    The tags whose bounding box lies on the x=0 symmetry axis, read from a live gmsh model.

  • resolve_region_sizes –

    The per-region size overrides to apply: each group's finest requested mesh_size, leaving out groups that request none.

  • apply_mesh_sizing –

    Apply the global element size to the whole domain, then each region override on top of it.

  • gmsh_set_global_size –

    Set the characteristic length at every geometry point -- the domain-wide target element size.

  • gmsh_set_region_size –

    Set the characteristic length on the points of the given bodies, overriding the global size there.

  • extract_entity_geometry –

    Read an entity's bounding box, center of mass and volume from a synchronized gmsh model.

Attributes:

  • BOUNDARY_SEP –

    The separator between the body names in a boundary group's name, as in AIR__N52_MAG_N or AIR__EXT.

  • EXTERNAL_SUFFIX –

    The name suffix of a body's external shell group: the faces it shares with no other body.

  • AXIS_SUFFIX –

    The name suffix of a body's boundary group on the x=0 symmetry axis of a 2D axisymmetric model.

  • AXIS_TOLERANCE_M –

    [m]: The largest distance from the symmetry axis at which an entity's bounding box still counts as on it.

BOUNDARY_SEP module-attribute

BOUNDARY_SEP = '__'

The separator between the body names in a boundary group's name, as in AIR__N52_MAG_N or AIR__EXT.

EXTERNAL_SUFFIX module-attribute

EXTERNAL_SUFFIX = 'EXT'

The name suffix of a body's external shell group: the faces it shares with no other body.

AXIS_SUFFIX module-attribute

AXIS_SUFFIX = 'AXIS'

The name suffix of a body's boundary group on the x=0 symmetry axis of a 2D axisymmetric model.

AXIS_TOLERANCE_M module-attribute

AXIS_TOLERANCE_M = 1e-06

[m]: The largest distance from the symmetry axis at which an entity's bounding box still counts as on it.

MeshConfig pydantic-model

Bases: ConfigArea

How the geometry is discretized for the gmsh backend: the STEP source, domain-wide element size, and typed gmsh knobs.

Config:

  • extra: forbid

Fields:

step pydantic-field

step: str = 'data/geometry.step'

Path to the STEP file the mesher imports; its build123d part names are parsed into materials and region tags (MeshEntity).

global_mesh_size pydantic-field

global_mesh_size: float = 0.001

h [m]: Domain-wide target element size, which a region's EntityTag.mesh_size overrides.

gmsh_options pydantic-field

gmsh_options: GmshOptions

Typed gmsh options applied before meshing.

deterministic pydantic-field

deterministic: bool = False

Pin meshing to one thread, so HXT's multithreading cannot vary the result and the same STEP and sizes always yield the identical mesh; slower, and part of the run identity.

resolved_gmsh_options

resolved_gmsh_options() -> GmshOptions

The options the mesher applies: gmsh_options, thread-pinned to 1 when deterministic.

Source code in src/femforge/meshing/config.py
def resolved_gmsh_options(self) -> GmshOptions:
    """The options the mesher applies: ``gmsh_options``, thread-pinned to 1 when ``deterministic``."""
    if not self.deterministic:
        return self.gmsh_options
    return self.gmsh_options.model_copy(update={"num_threads": 1})

SupportsMeshing

Bases: Protocol

The config surface the gmsh Mesher requires, which SimulationConfig satisfies structurally.

Attributes:

  • frame (Frame) –

    The solve frame, whose model_dim is the body dimension of the STEP.

  • mesh (MeshConfig) –

    The gmsh mesh settings (STEP source, element size, gmsh options).

  • domain (DomainConfig) –

    The materials + per-region tags the mesher matches STEP entities against.

frame instance-attribute

frame: Frame

The solve frame, whose model_dim is the body dimension of the STEP.

mesh instance-attribute

mesh: MeshConfig

The gmsh mesh settings (STEP source, element size, gmsh options).

domain instance-attribute

domain: DomainConfig

The materials + per-region tags the mesher matches STEP entities against.

GmshOptions pydantic-model

Bases: BaseModel

Typed gmsh meshing options; a knob left at None keeps gmsh's own default.

Config:

  • validate_assignment: True
  • use_attribute_docstrings: True

Fields:

import_labels pydantic-field

import_labels: bool = True

Import STEP/OpenCASCADE entity labels (Geometry.OCCImportLabels); the mesher needs these to match parts to materials.

terminal_output pydantic-field

terminal_output: bool = False

Print gmsh's progress to the terminal (General.Terminal); never changes the mesh, so it stays out of every identity.

algorithm_2d pydantic-field

2D meshing algorithm (Mesh.Algorithm); the default Frontal-Delaunay gives the all-triangle surface mesh HXT needs and meshes the bodies of a 2D model.

algorithm_3d pydantic-field

3D meshing algorithm (Mesh.Algorithm3D); the default HXT runs on num_threads and needs the all-triangle surface mesh recombine_all=False keeps.

recombine_all pydantic-field

recombine_all: bool = False

Recombine the simplex mesh into quads and hexes (Mesh.RecombineAll); keep it False, since WhitneyAVSolver needs tetrahedra and recombining makes 3D meshing fail.

mesh_size_min pydantic-field

mesh_size_min: float | None = None

h_min [m]: Lower clamp on element size (Mesh.MeshSizeMin); None inherits gmsh's default.

mesh_size_max pydantic-field

mesh_size_max: float | None = None

h_max [m]: Upper clamp on element size (Mesh.MeshSizeMax); None inherits gmsh's default.

optimize pydantic-field

optimize: bool = True

Run mesh optimization after generation (Mesh.Optimize).

smoothing pydantic-field

smoothing: int | None = None

Number of mesh smoothing steps (Mesh.Smoothing); None inherits gmsh's default.

num_threads pydantic-field

num_threads: int | None

Meshing threads (General.NumThreads): every physical core by default, None for gmsh's own default of one, and never part of an identity.

target_units pydantic-field

target_units: str | None = 'M'

Length unit OpenCASCADE converts the imported STEP to (Geometry.OCCTargetUnit): "M" keeps the pipeline in metres, None keeps the STEP's own unit.

apply

apply(set_number: Callable[[str, float], None] | None = None, set_string: Callable[[str, str], None] | None = None) -> None

Set each option that is not None under its Map(...) name, through set_number and set_string when given, else on the live gmsh option store.

Source code in src/femforge/meshing/gmsh_options.py
def apply(
    self,
    set_number: Callable[[str, float], None] | None = None,
    set_string: Callable[[str, str], None] | None = None,
) -> None:
    """Set each option that is not None under its ``Map(...)`` name, through ``set_number`` and ``set_string`` when given, else on the live gmsh option store."""
    # Imported here so loguru stays off the import path of every config.
    from femforge.common.log import log

    emit_number: Callable[[str, float], None] = _gmsh_set_number if set_number is None else set_number
    emit_string: Callable[[str, str], None] = _gmsh_set_string if set_string is None else set_string

    for option_name, value in mapped_items(self):
        # A str option (the OCC target unit) goes through setString; bool / IntEnum / int / float are all setNumber.
        if isinstance(value, str):
            log.bind(option=option_name, value=value).debug("Setting gmsh option.")
            emit_string(option_name, value)
        elif isinstance(value, (int, float)):
            log.bind(option=option_name, value=value).debug("Setting gmsh option.")
            emit_number(option_name, float(value))

MeshAlgorithm2D

Bases: IntEnum

A gmsh 2D meshing algorithm, valued as the integer Mesh.Algorithm takes.

Attributes:

  • MESH_ADAPT –

    MeshAdapt, the most robust on complex curved surfaces.

  • AUTOMATIC –

    Delaunay on plane surfaces and MeshAdapt on the rest.

  • DELAUNAY –

    Delaunay, the fastest on large plane surfaces.

  • FRONTAL_DELAUNAY –

    Frontal-Delaunay, gmsh's own default and the choice for high element quality.

MESH_ADAPT class-attribute instance-attribute

MESH_ADAPT = 1

MeshAdapt, the most robust on complex curved surfaces.

AUTOMATIC class-attribute instance-attribute

AUTOMATIC = 2

Delaunay on plane surfaces and MeshAdapt on the rest.

DELAUNAY class-attribute instance-attribute

DELAUNAY = 5

Delaunay, the fastest on large plane surfaces.

FRONTAL_DELAUNAY class-attribute instance-attribute

FRONTAL_DELAUNAY = 6

Frontal-Delaunay, gmsh's own default and the choice for high element quality.

MeshAlgorithm3D

Bases: IntEnum

A gmsh 3D meshing algorithm, valued as the integer Mesh.Algorithm3D takes.

Attributes:

  • DELAUNAY –

    Delaunay, gmsh's own default and its most robust 3D mesher.

  • FRONTAL –

    Frontal, Netgen's advancing-front tetrahedral mesher.

  • MMG3D –

    MMG3D, for anisotropic tetrahedral meshes.

  • HXT –

    HXT, a parallel reimplementation of Delaunay that spreads over the meshing threads.

DELAUNAY class-attribute instance-attribute

DELAUNAY = 1

Delaunay, gmsh's own default and its most robust 3D mesher.

FRONTAL class-attribute instance-attribute

FRONTAL = 4

Frontal, Netgen's advancing-front tetrahedral mesher.

MMG3D class-attribute instance-attribute

MMG3D = 7

MMG3D, for anisotropic tetrahedral meshes.

HXT class-attribute instance-attribute

HXT = 10

HXT, a parallel reimplementation of Delaunay that spreads over the meshing threads.

MeshBackend

Bases: Protocol

Meshes a job into a gmsh .msh plus its group records; each backend decides where gmsh runs.

Methods:

  • mesh –

    Mesh job into out_dir, raising MeshError when meshing fails.

mesh

mesh(job: MeshJob, out_dir: Path) -> MeshReport

Mesh job into out_dir, raising MeshError when meshing fails.

Source code in src/femforge/meshing/backend.py
def mesh(self, job: MeshJob, out_dir: Path) -> MeshReport:
    """Mesh ``job`` into ``out_dir``, raising [`MeshError`][femforge.meshing.backend.MeshError] when meshing fails."""
    ...

MeshJob pydantic-model

Bases: BaseModel

One meshing request as plain data: the mesh spec, the STEP to read, and the .msh to write.

Config:

  • use_attribute_docstrings: True

Fields:

spec pydantic-field

spec: MeshSpec

The mesh artifact's spec, which decides the mesh.

step pydantic-field

step: str

The solve-frame STEP to mesh.

msh pydantic-field

msh: str = BundleFile.GEOMETRY_MSH

The .msh file name to write in the output directory.

MeshReport pydantic-model

Bases: BaseModel

What one meshing job left: the .msh, its group records, and the gmsh release that wrote it.

Config:

  • use_attribute_docstrings: True

Fields:

msh pydantic-field

msh: str

The .msh file name in the output directory.

groups pydantic-field

The body and boundary group records and the element count.

gmsh_version pydantic-field

gmsh_version: str

The gmsh release that meshed the job.

duration_s pydantic-field

duration_s: float

t [s]: Wall time the meshing took.

MeshError

Bases: RuntimeError

Meshing failed: the STEP's bodies could not be grouped or meshed, or the mesh worker crashed or ran out of time.

MeshInputs dataclass

MeshInputs(frame: Frame, mesh: MeshConfig, domain: DomainConfig)

The config surface a mesher reads, rebuilt from a MeshJob alone.

Attributes:

  • frame (Frame) –

    A frame of the job's body dimension.

  • mesh (MeshConfig) –

    The job's STEP and mesh settings.

  • domain (DomainConfig) –

    Bare materials named by their tags, and the tag selectors with their element sizes.

frame instance-attribute

frame: Frame

A frame of the job's body dimension.

mesh instance-attribute

mesh: MeshConfig

The job's STEP and mesh settings.

domain instance-attribute

domain: DomainConfig

Bare materials named by their tags, and the tag selectors with their element sizes.

GmshMesher

Bases: MeshBackend

Meshes in this process with gmsh, which loads on the first job and is finalized after every one.

Methods:

  • mesh –

    Mesh job into out_dir with gmsh in this process.

mesh

mesh(job: MeshJob, out_dir: Path) -> MeshReport

Mesh job into out_dir with gmsh in this process.

Source code in src/femforge/meshing/backend.py
def mesh(self, job: MeshJob, out_dir: Path) -> MeshReport:
    """Mesh ``job`` into ``out_dir`` with gmsh in this process."""
    from femforge.meshing.gmsh_backend import mesh_with_gmsh

    return mesh_with_gmsh(job, out_dir)

WorkerMesher

WorkerMesher(*, python: str = executable, module: str = WORKER_MODULE, timeout_s: float | None = None)

Bases: MeshBackend

Meshes in a child Python process, so gmsh, its license and its crashes stay out of this one.

Parameters:

  • python (str, default: executable ) –

    The interpreter that runs the worker.

  • module (str, default: WORKER_MODULE ) –

    The worker module.

  • timeout_s (float | None, default: None ) –

    Seconds before a job is stopped; None waits for it.

Methods:

  • mesh –

    Mesh job into out_dir in a worker process, raising MeshError when the worker fails or leaves no outcome.

Source code in src/femforge/meshing/backend.py
def __init__(self, *, python: str = sys.executable, module: str = WORKER_MODULE, timeout_s: float | None = None) -> None:
    """Mesh each job with ``python -m module``, stopped after ``timeout_s`` seconds when given.

    Args:
        python: The interpreter that runs the worker.
        module: The worker module.
        timeout_s: Seconds before a job is stopped; None waits for it.
    """
    self.python: str = python
    self.module: str = module
    self.timeout_s: float | None = timeout_s

mesh

mesh(job: MeshJob, out_dir: Path) -> MeshReport

Mesh job into out_dir in a worker process, raising MeshError when the worker fails or leaves no outcome.

Source code in src/femforge/meshing/backend.py
def mesh(self, job: MeshJob, out_dir: Path) -> MeshReport:
    """Mesh ``job`` into ``out_dir`` in a worker process, raising [`MeshError`][femforge.meshing.backend.MeshError] when the worker fails or leaves no outcome."""
    from femforge.common.log import TELEMETRY_ENV, relay_record
    from femforge.execution.streaming import CommandResult, log_tail, run_streaming

    out_dir.mkdir(parents=True, exist_ok=True)
    # The worker meshes into the directory of an absolute msh, so the job file carries everything it needs.
    placed: MeshJob = job.model_copy(update={"step": str(Path(job.step).resolve()), "msh": str((out_dir / job.msh).resolve())})
    env: dict[str, str] = {name: value for name, value in os.environ.items() if name != TELEMETRY_ENV}
    env["PYTHONUTF8"] = "1"

    with tempfile.TemporaryDirectory(prefix="femforge-mesh-") as scratch:
        folder = Path(scratch)
        job_file: Path = folder / WORKER_JOB
        outcome_file: Path = folder / WORKER_OUTCOME
        _ = job_file.write_text(placed.model_dump_json(), encoding="utf-8")

        result: CommandResult = run_streaming(
            [self.python, "-m", self.module, str(job_file), str(outcome_file)],
            cwd=folder,
            log_path=folder / WORKER_LOG,
            on_line=functools.partial(relay_record, worker=WORKER_ROLE),
            timeout_s=self.timeout_s,
            env=env,
        )
        if not outcome_file.is_file():
            stopped: str = f"ran past its {self.timeout_s} s" if result.timed_out else f"exited with code {result.returncode}"
            raise MeshError(f"The mesh worker {stopped} without an outcome.\n{log_tail(result.log_path)}")

        outcome: MeshDone | MeshFailed = mesh_outcomes().validate_json(outcome_file.read_text(encoding="utf-8"))

    if isinstance(outcome, MeshFailed):
        raise MeshError(f"{outcome.error}: {outcome.message}")

    return outcome.report

MeshSpec pydantic-model

Bases: BaseModel

Everything that decides the Elmer mesh files and their group names, and nothing that only decides the solve.

Config:

  • use_attribute_docstrings: True

Fields:

step_key pydantic-field

step_key: str

The geometry identity (step_key).

model_dim pydantic-field

model_dim: Literal[2, 3]

Body dimension of the STEP, from the frame: 2D cross-section or 3D solids.

global_mesh_size pydantic-field

global_mesh_size: float

h [m]: Domain-wide target element size.

gmsh_options pydantic-field

gmsh_options: GmshOptions

The gmsh knobs (thread count and terminal output are HashExclude and drop out).

deterministic pydantic-field

deterministic: bool

Whether meshing was pinned to one thread for reproducibility.

tags pydantic-field

The domain's tags in domain order (an unnamed body's group name concatenates tag names in that order).

materials pydantic-field

materials: list[str]

The sorted material tags; an unnamed body's group name starts with its material tag.

TagSelectorSpec pydantic-model

Bases: BaseModel

The grouping-relevant half of an EntityTag: its selector and per-region size, never its conditions.

Config:

  • use_attribute_docstrings: True

Fields:

selector pydantic-field

selector: TagDict

The key=value pairs an entity must carry to join the group.

mesh_size pydantic-field

mesh_size: float | None = None

h [m]: The per-region element size, when set.

PhysicalGroup

PhysicalGroup(dim: int, gmsh_id: int, name: str, material: MaterialProperties, tags: list[EntityTag], entity_tags: list[int], authored: str | None = None)

A single gmsh physical group plus the metadata to reconstruct the matching Elmer body; both sides share the same name and integer id.

Parameters:

  • dim (int) –

    Topological dimension of the group.

  • gmsh_id (int) –

    The gmsh physical-group tag.

  • name (str) –

    The group name shared verbatim with Elmer.

  • material (MaterialProperties) –

    The material of every entity in the group.

  • tags (list[EntityTag]) –

    The EntityTag objects the group matched.

  • entity_tags (list[int]) –

    The gmsh entity tags in the group.

  • authored (str | None, default: None ) –

    The Name tag the entities were authored with, or None when the name is composed.

Methods:

Attributes:

  • dim –

    Topological dimension of the group (3 = volume in a 3D model, 2 = surface in a 2D one).

  • id –

    The integer the sif targets this region by: the gmsh tag until reconcile_ids sets the id ElmerGrid assigned.

  • name –

    The authored part name, else the compound MATERIAL_TAG1_TAG2 name; shared verbatim with Elmer.

  • authored –

    The Name tag the group's entities were authored with, or None when the name is composed.

  • material –

    The material of every entity in the group.

  • tags –

    EntityTag objects matched to this group (carry per-region overrides: fixed temperature, heat flux, magnetization direction).

  • entity_tags –

    The gmsh entity tags (volumes in 3D, surfaces in 2D) that make up this group.

Source code in src/femforge/meshing/groups.py
def __init__(self, dim: int, gmsh_id: int, name: str, material: MaterialProperties, tags: list[EntityTag], entity_tags: list[int], authored: str | None = None) -> None:
    """Record one body group.

    Args:
        dim: Topological dimension of the group.
        gmsh_id: The gmsh physical-group tag.
        name: The group name shared verbatim with Elmer.
        material: The material of every entity in the group.
        tags: The ``EntityTag`` objects the group matched.
        entity_tags: The gmsh entity tags in the group.
        authored: The ``Name`` tag the entities were authored with, or None when the name is composed.
    """
    self.dim = dim
    """Topological dimension of the group (3 = volume in a 3D model, 2 = surface in a 2D one)."""

    self.id = gmsh_id
    """The integer the sif targets this region by: the gmsh tag until [`reconcile_ids`][femforge.elmer.mesh_names.reconcile_ids] sets the id ElmerGrid assigned."""

    self.name = name
    """The authored part name, else the compound MATERIAL_TAG1_TAG2 name; shared verbatim with Elmer."""

    self.authored = authored
    """The ``Name`` tag the group's entities were authored with, or None when the name is composed."""

    self.material = material
    """The material of every entity in the group."""

    self.tags = tags
    """EntityTag objects matched to this group (carry per-region overrides: fixed temperature, heat flux, magnetization direction)."""

    self.entity_tags = entity_tags
    """The gmsh entity tags (volumes in 3D, surfaces in 2D) that make up this group."""

dim instance-attribute

dim = dim

Topological dimension of the group (3 = volume in a 3D model, 2 = surface in a 2D one).

id instance-attribute

id = gmsh_id

The integer the sif targets this region by: the gmsh tag until reconcile_ids sets the id ElmerGrid assigned.

name instance-attribute

name = name

The authored part name, else the compound MATERIAL_TAG1_TAG2 name; shared verbatim with Elmer.

authored instance-attribute

authored = authored

The Name tag the group's entities were authored with, or None when the name is composed.

material instance-attribute

material = material

The material of every entity in the group.

tags instance-attribute

tags = tags

EntityTag objects matched to this group (carry per-region overrides: fixed temperature, heat flux, magnetization direction).

entity_tags instance-attribute

entity_tags = entity_tags

The gmsh entity tags (volumes in 3D, surfaces in 2D) that make up this group.

physical_group_name

physical_group_name() -> str

The stable name the mesher and the sif writer both reference this region by, as MeshGroupSource requires.

Source code in src/femforge/meshing/groups.py
def physical_group_name(self) -> str:
    """The stable name the mesher and the sif writer both reference this region by, as [`MeshGroupSource`][femforge.common.protocols.MeshGroupSource] requires."""
    return self.name

MeshGroupsFile pydantic-model

Bases: BaseModel

groups.json: the group records and element count a mesh artifact carries beside ElmerGrid's files.

Config:

  • use_attribute_docstrings: True

Fields:

bodies pydantic-field

The body group records, in mesher order.

boundaries pydantic-field

The boundary group records, in mesher order.

element_count pydantic-field

element_count: int = 0

Elements of the body dimension in the mesh.

capture classmethod

capture(physical_groups: Iterable[_BodySource], boundary_groups: Iterable[_BoundarySource], element_count: int) -> MeshGroupsFile

Record the mesher's groups.

Parameters:

  • physical_groups (Iterable[_BodySource]) –

    The body groups, in mesher order.

  • boundary_groups (Iterable[_BoundarySource]) –

    The boundary groups, in mesher order.

  • element_count (int) –

    Elements of the body dimension in the mesh.

Source code in src/femforge/meshing/groups.py
@classmethod
def capture(cls, physical_groups: Iterable[_BodySource], boundary_groups: Iterable[_BoundarySource], element_count: int) -> MeshGroupsFile:
    """Record the mesher's groups.

    Args:
        physical_groups: The body groups, in mesher order.
        boundary_groups: The boundary groups, in mesher order.
        element_count: Elements of the body dimension in the mesh.
    """
    return cls(
        bodies=[
            BodyGroupRecord(
                dim=group.dim,
                name=group.name,
                material=group.material.tag,
                selectors=[dict(tag.selector) for tag in group.tags],
                entity_tags=list(group.entity_tags),
                authored=group.authored,
            )
            for group in physical_groups
        ],
        boundaries=[
            BoundaryGroupRecord(
                dim=group.dim,
                name=group.name,
                bodies=tuple(group.bodies),
                face_tags=list(group.entity_tags),
                on_axis=group.on_axis,
            )
            for group in boundary_groups
        ],
        element_count=element_count,
    )

load classmethod

load(sim_dir: str | Path) -> MeshGroupsFile

Read groups.json from sim_dir.

Source code in src/femforge/meshing/groups.py
@classmethod
def load(cls, sim_dir: str | Path) -> MeshGroupsFile:
    """Read ``groups.json`` from ``sim_dir``."""
    return cls.model_validate_json((Path(sim_dir) / BundleFile.GROUPS).read_text(encoding="utf-8"))

write

write(sim_dir: str | Path) -> Path

Write groups.json into sim_dir and return its path.

Source code in src/femforge/meshing/groups.py
def write(self, sim_dir: str | Path) -> Path:
    """Write ``groups.json`` into ``sim_dir`` and return its path."""
    path: Path = Path(sim_dir) / BundleFile.GROUPS
    _ = path.write_text(self.model_dump_json(indent=2), encoding="utf-8")
    return path

BodyGroupRecord pydantic-model

Bases: BaseModel

One body group of a cached mesh: what it takes to rebuild its PhysicalGroup against a config.

Config:

  • use_attribute_docstrings: True

Fields:

dim pydantic-field

dim: int

Topological dimension of the group.

name pydantic-field

name: str

The group name shared verbatim with Elmer.

material pydantic-field

material: str

The material tag.

selectors pydantic-field

selectors: list[TagDict]

The selectors of the tags the group matched, in domain order.

entity_tags pydantic-field

entity_tags: list[int]

The gmsh entity tags in the group.

authored pydantic-field

authored: str | None = None

The Name tag the group was named by, or None when its name is composed.

BoundaryGroupRecord pydantic-model

Bases: BaseModel

One boundary group of a cached mesh.

Config:

  • use_attribute_docstrings: True

Fields:

dim pydantic-field

dim: int

Topological dimension of the group.

name pydantic-field

name: str

The compound boundary name shared verbatim with Elmer.

bodies pydantic-field

bodies: tuple[str, ...] = ()

The one or two body group names the boundary borders.

face_tags pydantic-field

face_tags: list[int]

The gmsh entity tags in the group.

on_axis pydantic-field

on_axis: bool = False

Whether the group lies on the symmetry axis of a 2D axisymmetric model.

BoundaryGroup

BoundaryGroup(gmsh_id: int, name: str, bodies: tuple[str, ...], face_tags: list[int], dim: int = 2, on_axis: bool = False)

A single gmsh boundary physical group derived from body adjacency — the one-dimension-down sibling of PhysicalGroup, targeted by Elmer as Target Boundaries.

Names encode the bordering bodies: <BODYA>__<BODYB> (interface), <BODY>__EXT (external shell), or <BODY>__AXIS (symmetry axis).

Parameters:

  • gmsh_id (int) –

    The gmsh physical-group tag.

  • name (str) –

    The compound name shared verbatim with Elmer.

  • bodies (tuple[str, ...]) –

    The one or two body group names the group borders.

  • face_tags (list[int]) –

    The gmsh entity tags in the group.

  • dim (int, default: 2 ) –

    Topological dimension of the group.

  • on_axis (bool, default: False ) –

    Whether every entity lies on the symmetry axis of a 2D axisymmetric model.

Methods:

Attributes:

  • dim –

    Topological dimension of the group -- one below the bodies it separates (2 = surface in a 3D model, 1 = curve in a 2D one), mirroring PhysicalGroup.dim.

  • id –

    The integer the sif targets this boundary by: the gmsh tag until reconcile_ids sets the id ElmerGrid assigned.

  • name –

    The compound BODYA__BODYB, BODY__EXT or BODY__AXIS name, shared verbatim with Elmer.

  • bodies –

    The 1 (external or axis) or 2 (interface) body group names this boundary borders.

  • entity_tags –

    The gmsh entity tags (surfaces in 3D, curves in 2D) that make up this group, mirroring PhysicalGroup.entity_tags.

  • on_axis –

    True when every entity of this single-body group lies on the x=0 symmetry axis of a 2D axisymmetric model.

  • is_external (bool) –

    True when this is a body's true external (far-field) shell: bordered by exactly one body and not on the symmetry axis.

  • is_axis (bool) –

    True when this is a body's symmetry-axis boundary (the <BODY>__AXIS group of a 2D axisymmetric model).

Source code in src/femforge/meshing/boundary.py
def __init__(self, gmsh_id: int, name: str, bodies: tuple[str, ...], face_tags: list[int], dim: int = 2, on_axis: bool = False):
    """Record one boundary group.

    Args:
        gmsh_id: The gmsh physical-group tag.
        name: The compound name shared verbatim with Elmer.
        bodies: The one or two body group names the group borders.
        face_tags: The gmsh entity tags in the group.
        dim: Topological dimension of the group.
        on_axis: Whether every entity lies on the symmetry axis of a 2D axisymmetric model.
    """
    self.dim = dim
    """Topological dimension of the group -- one below the bodies it separates (2 = surface in a 3D model, 1 = curve in a 2D one), mirroring ``PhysicalGroup.dim``."""

    self.id = gmsh_id
    """The integer the sif targets this boundary by: the gmsh tag until [`reconcile_ids`][femforge.elmer.mesh_names.reconcile_ids] sets the id ElmerGrid assigned."""

    self.name = name
    """The compound ``BODYA__BODYB``, ``BODY__EXT`` or ``BODY__AXIS`` name, shared verbatim with Elmer."""

    self.bodies = bodies
    """The 1 (external or axis) or 2 (interface) body group names this boundary borders."""

    self.entity_tags = face_tags
    """The gmsh entity tags (surfaces in 3D, curves in 2D) that make up this group, mirroring ``PhysicalGroup.entity_tags``."""

    self.on_axis = on_axis
    """True when every entity of this single-body group lies on the x=0 symmetry axis of a 2D axisymmetric model."""

dim instance-attribute

dim = dim

Topological dimension of the group -- one below the bodies it separates (2 = surface in a 3D model, 1 = curve in a 2D one), mirroring PhysicalGroup.dim.

id instance-attribute

id = gmsh_id

The integer the sif targets this boundary by: the gmsh tag until reconcile_ids sets the id ElmerGrid assigned.

name instance-attribute

name = name

The compound BODYA__BODYB, BODY__EXT or BODY__AXIS name, shared verbatim with Elmer.

bodies instance-attribute

bodies = bodies

The 1 (external or axis) or 2 (interface) body group names this boundary borders.

entity_tags instance-attribute

entity_tags = face_tags

The gmsh entity tags (surfaces in 3D, curves in 2D) that make up this group, mirroring PhysicalGroup.entity_tags.

on_axis instance-attribute

on_axis = on_axis

True when every entity of this single-body group lies on the x=0 symmetry axis of a 2D axisymmetric model.

is_external property

is_external: bool

True when this is a body's true external (far-field) shell: bordered by exactly one body and not on the symmetry axis.

is_axis property

is_axis: bool

True when this is a body's symmetry-axis boundary (the <BODY>__AXIS group of a 2D axisymmetric model).

physical_group_name

physical_group_name() -> str

The stable name the mesher and the sif writer both reference this boundary by, as MeshGroupSource requires.

Source code in src/femforge/meshing/boundary.py
def physical_group_name(self) -> str:
    """The stable name the mesher and the sif writer both reference this boundary by, as [`MeshGroupSource`][femforge.common.protocols.MeshGroupSource] requires."""
    return self.name

BoundaryPartition dataclass

BoundaryPartition(name: str, bodies: tuple[str, ...], face_tags: tuple[int, ...], on_axis: bool = False)

One planned boundary group: the output of the pure adjacency partition, before gmsh assigns it a physical-group id.

Attributes:

  • name (str) –

    The group name: A__B for an interface, A__EXT for an external shell, A__AXIS for a symmetry-axis boundary.

  • bodies (tuple[str, ...]) –

    The one or two body group names the faces border, sorted.

  • face_tags (tuple[int, ...]) –

    The boundary entity tags in the group, sorted.

  • on_axis (bool) –

    Whether the group is a single body's boundary on the symmetry axis.

name instance-attribute

name: str

The group name: A__B for an interface, A__EXT for an external shell, A__AXIS for a symmetry-axis boundary.

bodies instance-attribute

bodies: tuple[str, ...]

The one or two body group names the faces border, sorted.

face_tags instance-attribute

face_tags: tuple[int, ...]

The boundary entity tags in the group, sorted.

on_axis class-attribute instance-attribute

on_axis: bool = False

Whether the group is a single body's boundary on the symmetry axis.

ResolvedRegionMeshSize dataclass

ResolvedRegionMeshSize(volume_tags: tuple[int, ...], size: float)

A resolved per-region target element size and the volume tags it applies to.

Attributes:

  • volume_tags (tuple[int, ...]) –

    The gmsh tags of the region's bodies (volumes in 3D, surfaces in 2D).

  • size (float) –

    h [m]: Target element size.

volume_tags instance-attribute

volume_tags: tuple[int, ...]

The gmsh tags of the region's bodies (volumes in 3D, surfaces in 2D).

size instance-attribute

size: float

h [m]: Target element size.

EntityGeometry dataclass

EntityGeometry(dim: int, tag: int, bbox_min: tuple[float, float, float], bbox_max: tuple[float, float, float], center_of_mass: tuple[float, float, float], volume: float)

Axis-aligned bounding box, center of mass and volume of one gmsh entity.

Attributes:

dim instance-attribute

dim: int

Topological dimension of the entity.

tag instance-attribute

tag: int

The gmsh entity tag.

bbox_min instance-attribute

bbox_min: tuple[float, float, float]

[m]: Lower corner of the axis-aligned bounding box.

bbox_max instance-attribute

bbox_max: tuple[float, float, float]

[m]: Upper corner of the axis-aligned bounding box.

center_of_mass instance-attribute

center_of_mass: tuple[float, float, float]

[m]: Center of mass.

volume instance-attribute

volume: float

V [?]: Volume, area or length of the entity by dim, in m³, m² or m.

extent property

extent: tuple[float, float, float]

[m]: Size of the bounding box along each axis.

mesh_inputs

mesh_inputs(job: MeshJob) -> MeshInputs

The mesher's inputs for job: the frame of its dimension, its mesh settings, and a domain of bare materials and tag selectors.

Source code in src/femforge/meshing/backend.py
def mesh_inputs(job: MeshJob) -> MeshInputs:
    """The mesher's inputs for ``job``: the frame of its dimension, its mesh settings, and a domain of bare materials and tag selectors."""
    spec: MeshSpec = job.spec
    frame: Frame = Solid() if spec.model_dim == 3 else Axisymmetric()
    mesh = MeshConfig(
        step=job.step,
        global_mesh_size=spec.global_mesh_size,
        gmsh_options=spec.gmsh_options,
        deterministic=spec.deterministic,
    )
    materials: list[MaterialProperties] = [MaterialProperties(name=tag, tag=tag) for tag in spec.materials]
    tags: list[EntityTag] = [EntityTag(selector=dict(tag.selector), mesh_size=tag.mesh_size) for tag in spec.tags]

    return MeshInputs(frame=frame, mesh=mesh, domain=DomainConfig(materials=materials, tags=tags))

block_gmsh

block_gmsh() -> None

Make every later import gmsh in this process raise ImportError, so an entry point can prove it never loads gmsh.

Source code in src/femforge/meshing/firewall.py
def block_gmsh() -> None:
    """Make every later ``import gmsh`` in this process raise ImportError, so an entry point can prove it never loads gmsh."""
    if GMSH in sys.modules:
        raise RuntimeError("gmsh is already loaded in this process, so it can no longer be blocked.")

    if not any(isinstance(finder, _GmshBlocker) for finder in sys.meta_path):
        sys.meta_path.insert(0, _GmshBlocker())

raise_on_duplicate_names

raise_on_duplicate_names(groups: Sequence[PhysicalGroup]) -> None

Raise when two body groups share a name, e.g. one Name authored on parts of two materials.

Source code in src/femforge/meshing/groups.py
def raise_on_duplicate_names(groups: Sequence[PhysicalGroup]) -> None:
    """Raise when two body groups share a name, e.g. one ``Name`` authored on parts of two materials."""
    by_name: dict[str, list[PhysicalGroup]] = {}
    for group in groups:
        by_name.setdefault(group.name, []).append(group)

    clashes = {name: [group.material.tag for group in clashing] for name, clashing in by_name.items() if len(clashing) > 1}
    if clashes:
        detail = "; ".join(f"{name!r} on materials {materials}" for name, materials in clashes.items())
        raise ValueError(f"Body names must be unique: {detail}. Parts share a name only when they share a material and matched tags.")

format_physical_groups

format_physical_groups(groups: Sequence[PhysicalGroup], *, total_entities: int) -> str

Multi-line material → tag-group → physical-group summary for one log record (embeds newlines).

Source code in src/femforge/meshing/groups.py
def format_physical_groups(groups: Sequence[PhysicalGroup], *, total_entities: int) -> str:
    """Multi-line material → tag-group → physical-group summary for one log record (embeds newlines)."""
    by_material: dict[str, list[PhysicalGroup]] = {}
    for group in groups:
        by_material.setdefault(group.material.name, []).append(group)

    # (label, group) rows; a None group marks a material header above its tag groups.
    rows: list[tuple[str, PhysicalGroup | None]] = []
    for material_name, material_groups in by_material.items():
        if len(material_groups) == 1 and not material_groups[0].tags:
            rows.append((f"  {material_name}", material_groups[0]))
            continue

        rows.append((f"  {material_name}", None))
        for group in material_groups:
            label = ", ".join(tag.name for tag in group.tags) or "(untagged)"
            rows.append((f"    {label}", group))

    label_width: int = max((len(label) for label, group in rows if group is not None), default=0)

    lines: list[str] = ["", f"{total_entities} Entities → {len(groups)} Physical Groups:"]
    for label, group in rows:
        if group is None:
            lines.append(label)
        else:
            lines.append(f"{label:<{label_width}} {len(group.entity_tags):>4}  → {group.name}")

    return "\n".join(lines)

format_boundary_groups

format_boundary_groups(groups: Sequence[BoundaryGroup]) -> str

Multi-line boundary-group summary for one log record (embeds newlines).

Source code in src/femforge/meshing/groups.py
def format_boundary_groups(groups: Sequence[BoundaryGroup]) -> str:
    """Multi-line boundary-group summary for one log record (embeds newlines)."""
    lines: list[str] = ["", f"{len(groups)} Boundary Groups:"]
    max_group_name = max((len(group.name) for group in groups), default=0)
    for group in groups:
        lines.append(f"  {group.name:<{max_group_name}} {len(group.entity_tags):>4}  → id {group.id}")

    return "\n".join(lines)

partition_boundary_faces

partition_boundary_faces(face_to_bodies: Mapping[int, set[str]], *, axis_faces: frozenset[int] = frozenset()) -> list[BoundaryPartition]

Group every boundary face by the bodies it borders into sorted interface (A__B), external (A__EXT) and symmetry-axis (A__AXIS) partitions.

A face in axis_faces becomes an axis partition only when it borders a single body.

Source code in src/femforge/meshing/boundary.py
def partition_boundary_faces(face_to_bodies: Mapping[int, set[str]], *, axis_faces: frozenset[int] = frozenset()) -> list[BoundaryPartition]:
    """Group every boundary face by the bodies it borders into sorted interface (``A__B``), external (``A__EXT``) and symmetry-axis (``A__AXIS``) partitions.

    A face in ``axis_faces`` becomes an axis partition only when it borders a single body.
    """
    grouped: dict[tuple[tuple[str, ...], bool], list[int]] = defaultdict(list)
    for face_tag, bodies in face_to_bodies.items():
        key = tuple(sorted(bodies))
        if not key:
            continue  # a face bounding no known body can't be named; skip rather than invent a group.
        on_axis: bool = len(key) == 1 and face_tag in axis_faces
        grouped[(key, on_axis)].append(face_tag)

    partitions: list[BoundaryPartition] = []
    for bodies, on_axis in sorted(grouped):
        if on_axis:
            name = f"{bodies[0]}{BOUNDARY_SEP}{AXIS_SUFFIX}"
        elif len(bodies) == 1:
            name = f"{bodies[0]}{BOUNDARY_SEP}{EXTERNAL_SUFFIX}"
        else:
            name = BOUNDARY_SEP.join(bodies)

        face_tags = tuple(sorted(grouped[(bodies, on_axis)]))
        partition = BoundaryPartition(
            name=name,
            bodies=bodies,
            face_tags=face_tags,
            on_axis=on_axis,
        )
        partitions.append(partition)

    return partitions

read_face_adjacency

read_face_adjacency(body_groups: Iterable[tuple[str, Iterable[int]]], body_dim: int = 3) -> dict[int, set[str]]

Map each boundary entity of the body_dim body groups to the names of the bodies it bounds, read from a synchronized gmsh model.

Source code in src/femforge/meshing/boundary.py
def read_face_adjacency(
    body_groups: Iterable[tuple[str, Iterable[int]]],
    body_dim: int = 3,
) -> dict[int, set[str]]:  # pragma: no cover - needs a live gmsh model (exercised by the gated integration tests)
    """Map each boundary entity of the ``body_dim`` body groups to the names of the bodies it bounds, read from a synchronized gmsh model."""
    import gmsh

    face_to_bodies: dict[int, set[str]] = defaultdict(set)
    for name, volume_tags in body_groups:
        for volume_tag in volume_tags:
            # combined=False keeps each body's own boundary (so a shared face appears under both bodies); oriented=False yields positive tags.
            for _, face_tag in gmsh.model.getBoundary([(body_dim, volume_tag)], combined=False, oriented=False):
                face_to_bodies[face_tag].add(name)
    return dict(face_to_bodies)

axis_entity_tags

axis_entity_tags(tags: Iterable[int], dim: int, tol: float = AXIS_TOLERANCE_M) -> frozenset[int]

The tags whose bounding box lies on the x=0 symmetry axis, read from a live gmsh model.

Parameters:

  • tags (Iterable[int]) –

    The gmsh entity tags to test.

  • dim (int) –

    Topological dimension of the entities.

  • tol (float, default: AXIS_TOLERANCE_M ) –

    The largest distance from the axis, in metres, that still counts as on it.

Source code in src/femforge/meshing/boundary.py
def axis_entity_tags(
    tags: Iterable[int],
    dim: int,
    tol: float = AXIS_TOLERANCE_M,
) -> frozenset[int]:  # pragma: no cover - needs a live gmsh model (exercised by the gated integration tests)
    """The ``tags`` whose bounding box lies on the x=0 symmetry axis, read from a live gmsh model.

    Args:
        tags: The gmsh entity tags to test.
        dim: Topological dimension of the entities.
        tol: The largest distance from the axis, in metres, that still counts as on it.
    """
    on_axis: set[int] = set()
    for tag in tags:
        geometry = extract_entity_geometry(dim, tag)
        if abs(geometry.bbox_min[0]) <= tol and abs(geometry.bbox_max[0]) <= tol:
            on_axis.add(tag)
    return frozenset(on_axis)

resolve_region_sizes

resolve_region_sizes(groups: Iterable[_SizedRegion]) -> list[ResolvedRegionMeshSize]

The per-region size overrides to apply: each group's finest requested mesh_size, leaving out groups that request none.

Source code in src/femforge/meshing/mesh_sizing.py
def resolve_region_sizes(groups: Iterable[_SizedRegion]) -> list[ResolvedRegionMeshSize]:
    """The per-region size overrides to apply: each group's finest requested ``mesh_size``, leaving out groups that request none."""
    assignments: list[ResolvedRegionMeshSize] = []
    for group in groups:
        requested: list[float] = [tag.mesh_size for tag in group.tags if tag.mesh_size is not None]
        if not requested:
            continue

        region_size = ResolvedRegionMeshSize(
            volume_tags=tuple(group.entity_tags),
            size=min(requested),
        )
        assignments.append(region_size)

    return assignments

apply_mesh_sizing

apply_mesh_sizing(global_size: float, region_sizes: Iterable[ResolvedRegionMeshSize], *, set_global: Callable[[float], None], set_region: Callable[[Sequence[int], float], None]) -> None

Apply the global element size to the whole domain, then each region override on top of it.

Parameters:

Source code in src/femforge/meshing/mesh_sizing.py
def apply_mesh_sizing(
    global_size: float,
    region_sizes: Iterable[ResolvedRegionMeshSize],
    *,
    set_global: Callable[[float], None],
    set_region: Callable[[Sequence[int], float], None],
) -> None:
    """Apply the global element size to the whole domain, then each region override on top of it.

    Args:
        global_size: Domain-wide target element size in metres.
        region_sizes: The per-region overrides, applied in order.
        set_global: Applies the domain-wide size.
        set_region: Applies one size to the given body tags.
    """
    set_global(global_size)
    for region in region_sizes:
        set_region(region.volume_tags, region.size)

gmsh_set_global_size

gmsh_set_global_size(size: float) -> None

Set the characteristic length at every geometry point -- the domain-wide target element size.

Source code in src/femforge/meshing/mesh_sizing.py
def gmsh_set_global_size(size: float) -> None:  # pragma: no cover - drives live gmsh, exercised by the gated integration test
    """Set the characteristic length at every geometry point -- the domain-wide target element size."""
    import gmsh

    gmsh.model.mesh.setSize(gmsh.model.getEntities(0), size)

gmsh_set_region_size

gmsh_set_region_size(volume_tags: Sequence[int], size: float, body_dim: int = 3) -> None

Set the characteristic length on the points of the given bodies, overriding the global size there.

Parameters:

  • volume_tags (Sequence[int]) –

    The gmsh tags of the bodies to size.

  • size (float) –

    Target element size in metres.

  • body_dim (int, default: 3 ) –

    Topological dimension of the bodies.

Source code in src/femforge/meshing/mesh_sizing.py
def gmsh_set_region_size(volume_tags: Sequence[int], size: float, body_dim: int = 3) -> None:  # pragma: no cover - drives live gmsh, exercised by the gated integration test
    """Set the characteristic length on the points of the given bodies, overriding the global size there.

    Args:
        volume_tags: The gmsh tags of the bodies to size.
        size: Target element size in metres.
        body_dim: Topological dimension of the bodies.
    """
    import gmsh

    body_dimtags = [(body_dim, tag) for tag in volume_tags]
    points: list[tuple[int, int]] = gmsh.model.getBoundary(body_dimtags, combined=False, oriented=False, recursive=True)
    gmsh.model.mesh.setSize(points, size)

extract_entity_geometry

extract_entity_geometry(dim: int, tag: int) -> EntityGeometry

Read an entity's bounding box, center of mass and volume from a synchronized gmsh model.

Source code in src/femforge/meshing/geometry_utils.py
def extract_entity_geometry(dim: int, tag: int) -> EntityGeometry:  # pragma: no cover - needs a live gmsh model
    """Read an entity's bounding box, center of mass and volume from a synchronized gmsh model."""
    import gmsh

    bb = gmsh.model.getBoundingBox(dim, tag)
    com = gmsh.model.occ.getCenterOfMass(dim, tag)
    volume: float = gmsh.model.occ.getMass(dim, tag)
    return EntityGeometry(
        dim=dim,
        tag=tag,
        bbox_min=(bb[0], bb[1], bb[2]),
        bbox_max=(bb[3], bb[4], bb[5]),
        center_of_mass=(com[0], com[1], com[2]),
        volume=volume,
    )