Skip to content

Sizing Constraints

This page documents the sizing constraint classes for local mesh refinement.

Overview

Sizing constraints define regions where specific element sizes should be used. Multiple constraints can be combined: where they overlap, the minimum size wins.

The recommended way to apply sizing constraints is to pass them as a local_sizing=[...] list of dicts to dataset.mmg.remesh(...). The corresponding dataclasses (SphereSize, BoxSize, ...) are also exported for callers that prefer typed objects.

Sizing Classes

SphereSize

mmgpy.SphereSize dataclass

SphereSize(center: NDArray[float64], radius: float, size: float)

Bases: SizingConstraint

Uniform size within a spherical region.

Parameters

center : array_like Center of the sphere, shape (dim,). radius : float Radius of the sphere. Must be positive. size : float Target edge size within the sphere. Must be positive.

__post_init__

__post_init__() -> None

Coerce center to float64 and validate positive radius/size.

Raises

ValueError If radius or size is not strictly positive.

compute_sizes

compute_sizes(vertices: NDArray[float64]) -> NDArray[np.float64]

Return self.size inside the sphere, np.inf elsewhere.

Returns

NDArray[np.float64] Per-vertex target size, shape (n_vertices,).

options: show_root_heading: true

BoxSize

mmgpy.BoxSize dataclass

BoxSize(bounds: NDArray[float64], size: float)

Bases: SizingConstraint

Uniform size within a box region.

Parameters

bounds : array_like Box bounds as [[xmin, ymin, zmin], [xmax, ymax, zmax]] for 3D or [[xmin, ymin], [xmax, ymax]] for 2D. size : float Target edge size within the box. Must be positive.

__post_init__

__post_init__() -> None

Coerce bounds to float64 and validate shape/size.

Raises

ValueError If bounds does not have shape (2, dim) or size is not strictly positive.

compute_sizes

compute_sizes(vertices: NDArray[float64]) -> NDArray[np.float64]

Return self.size inside the box, np.inf elsewhere.

Returns

NDArray[np.float64] Per-vertex target size, shape (n_vertices,).

options: show_root_heading: true

CylinderSize

mmgpy.CylinderSize dataclass

CylinderSize(
    point1: NDArray[float64], point2: NDArray[float64], radius: float, size: float
)

Bases: SizingConstraint

Uniform size within a cylindrical region.

Parameters

point1 : array_like First endpoint of cylinder axis, shape (3,). point2 : array_like Second endpoint of cylinder axis, shape (3,). radius : float Radius of the cylinder. Must be positive. size : float Target edge size within the cylinder. Must be positive.

__post_init__

__post_init__() -> None

Coerce endpoints to float64 and validate positive radius/size.

Raises

ValueError If radius or size is not strictly positive.

compute_sizes

compute_sizes(vertices: NDArray[float64]) -> NDArray[np.float64]

Return self.size inside the cylinder, np.inf elsewhere.

Returns

NDArray[np.float64] Per-vertex target size, shape (n_vertices,).

Raises

ValueError If the cylinder axis (point2 - point1) has zero length.

options: show_root_heading: true

PointSize

mmgpy.PointSize dataclass

PointSize(
    point: NDArray[float64], near_size: float, far_size: float, influence_radius: float
)

Bases: SizingConstraint

Distance-based sizing from a point.

Size varies linearly from near_size at the point to far_size at influence_radius distance.

Parameters

point : array_like Reference point, shape (dim,). near_size : float Target size at the reference point. Must be positive. far_size : float Target size at influence_radius distance and beyond. Must be positive. influence_radius : float Distance over which size transitions from near_size to far_size. Must be positive.

__post_init__

__post_init__() -> None

Coerce point to float64 and validate positive sizes/radius.

Raises

ValueError If near_size, far_size, or influence_radius is not strictly positive.

compute_sizes

compute_sizes(vertices: NDArray[float64]) -> NDArray[np.float64]

Linearly interpolate from near_size to far_size by distance.

Returns

NDArray[np.float64] Per-vertex target size, shape (n_vertices,).

options: show_root_heading: true

SizingConstraint (Base Class)

mmgpy.SizingConstraint dataclass

SizingConstraint()

Bases: ABC

Base class for sizing constraints.

compute_sizes abstractmethod

compute_sizes(vertices: NDArray[float64]) -> NDArray[np.float64]

Compute target size at each vertex.

Parameters

vertices : NDArray[np.float64] Vertex coordinates, shape (n_vertices, dim).

Returns

NDArray[np.float64] Target size at each vertex, shape (n_vertices,). Use np.inf for vertices where this constraint doesn't apply.

options: show_root_heading: true

Accessor Usage

Pass sizing specifications to mesh.mmg.remesh(...) via the local_sizing keyword. Each entry is a dict with a "shape" key ("sphere", "box", "cylinder", or "from_point") plus the matching parameters:

import pyvista as pv
import mmgpy  # noqa: F401  -- registers reader/writer + accessor

mesh = pv.read("input.mesh")

remeshed = mesh.mmg.remesh(
    hmax=0.1,
    local_sizing=[
        {"shape": "sphere", "center": [0.5, 0.5, 0.5], "radius": 0.2, "size": 0.01},
        {"shape": "box", "bounds": [[0, 0, 0], [0.3, 0.3, 0.3]], "size": 0.02},
        {
            "shape": "cylinder",
            "point1": [0, 0, 0],
            "point2": [0, 0, 1],
            "radius": 0.1,
            "size": 0.01,
        },
        {
            "shape": "from_point",
            "point": [0.5, 0.5, 0.5],
            "near_size": 0.01,
            "far_size": 0.1,
            "influence_radius": 0.5,
        },
    ],
)

Available shapes

Shape Required parameters
sphere center, radius, size
box bounds (a [[xmin, ymin, zmin], [xmax, ymax, zmax]] pair), size
cylinder point1, point2, radius, size (3D only)
from_point point, near_size, far_size, influence_radius

Utility Functions

mmgpy.sizing.apply_sizing_constraints

apply_sizing_constraints(
    mesh: MmgMesh3D | MmgMesh2D | MmgMeshS,
    constraints: list[SizingConstraint],
    existing_metric: NDArray[float64] | None = None,
) -> None

Apply sizing constraints to a mesh by setting its metric field.

Parameters

mesh : MmgMesh3D | MmgMesh2D | MmgMeshS Mesh to apply sizing to. constraints : list[SizingConstraint] List of sizing constraints. existing_metric : NDArray[np.float64] | None Existing metric field to combine with. If provided, minimum size wins.

options: show_root_heading: true

mmgpy.sizing.compute_sizes_from_constraints

compute_sizes_from_constraints(
    vertices: NDArray[float64], constraints: list[SizingConstraint]
) -> NDArray[np.float64]

Compute combined sizing from multiple constraints.

Multiple constraints are combined by taking the minimum size at each vertex (finest mesh wins).

Parameters

vertices : NDArray[np.float64] Vertex coordinates, shape (n_vertices, dim). constraints : list[SizingConstraint] List of sizing constraints.

Returns

NDArray[np.float64] Combined target size at each vertex, shape (n_vertices,).

Raises

ValueError If constraints is empty.

options: show_root_heading: true

mmgpy.sizing.sizes_to_metric

sizes_to_metric(sizes: NDArray[float64]) -> NDArray[np.float64]

Convert scalar sizes to metric tensor format.

Parameters

sizes : NDArray[np.float64] Target sizes at each vertex, shape (n_vertices,).

Returns

NDArray[np.float64] Metric field suitable for mesh["metric"], shape (n_vertices, 1).

options: show_root_heading: true

Usage Examples

Basic Usage

import pyvista as pv
import mmgpy  # noqa: F401

mesh = pv.read("input.mesh")

remeshed = mesh.mmg.remesh(
    hmax=0.1,
    local_sizing=[
        {"shape": "sphere", "center": [0.5, 0.5, 0.5], "radius": 0.2, "size": 0.01},
    ],
)

Multiple Regions

remeshed = mesh.mmg.remesh(
    hmax=0.1,
    local_sizing=[
        # Fine region
        {"shape": "sphere", "center": [0.3, 0.5, 0.5], "radius": 0.1, "size": 0.005},
        # Medium region
        {"shape": "box", "bounds": [[0.5, 0, 0], [1, 1, 1]], "size": 0.02},
        # Graded region
        {
            "shape": "from_point",
            "point": [0.8, 0.5, 0.5],
            "near_size": 0.01,
            "far_size": 0.05,
            "influence_radius": 0.3,
        },
    ],
)

Using the Dataclasses Directly

import numpy as np
import pyvista as pv
import mmgpy  # noqa: F401
from mmgpy import SphereSize, BoxSize
from mmgpy.sizing import compute_sizes_from_constraints, sizes_to_metric

mesh = pv.read("input.mesh")

constraints = [
    SphereSize(
        center=np.array([0.5, 0.5, 0.5]),
        radius=0.2,
        size=0.01,
    ),
    BoxSize(
        bounds=np.array([[0, 0, 0], [0.3, 0.3, 0.3]]),
        size=0.02,
    ),
]

# Convert directly to an isotropic metric and remesh through point_data
vertices = np.asarray(mesh.points)
sizes = compute_sizes_from_constraints(vertices, constraints)
mesh.point_data["metric"] = sizes_to_metric(sizes)
remeshed = mesh.mmg.remesh()

Workflow with Validation

import pyvista as pv
import mmgpy  # noqa: F401

mesh = pv.read("input.mesh")

remeshed = mesh.mmg.remesh(
    hmax=0.1,
    verbose=-1,
    local_sizing=[
        {"shape": "sphere", "center": [0.5, 0.5, 0.5], "radius": 0.2, "size": 0.01},
    ],
)

# Inspect the size map MMG used (also writes point_data["metric"] in place)
sizes = remeshed.mmg.build_size_map()
print(f"Metric sizes: {sizes.min():.4f} to {sizes.max():.4f}")

assert remeshed.mmg.validate(), "remeshed mesh failed validation"

How Sizing Works

  1. Constraint Definition, each constraint defines a region and a target size.
  2. Size Computation, for each vertex, MMG computes the size implied by every constraint.
  3. Minimum Selection, where constraints overlap, the smallest size wins.
  4. Metric Conversion, sizes are converted to isotropic metric tensors.
  5. Remeshing, MMG uses the metric field to guide remeshing.

Constraints become the point_data["metric"] array on the remeshed dataset (an isotropic scalar). Build it manually if you want full control:

import numpy as np
import pyvista as pv
import mmgpy  # noqa: F401
from mmgpy import SphereSize, BoxSize
from mmgpy.sizing import compute_sizes_from_constraints
import mmgpy.metrics as metrics

mesh = pv.read("input.mesh")
vertices = np.asarray(mesh.points)

constraints = [
    SphereSize(center=np.array([0.5, 0.5, 0.5]), radius=0.2, size=0.01),
    BoxSize(bounds=np.array([[0, 0, 0], [0.3, 0.3, 0.3]]), size=0.02),
]
sizes = compute_sizes_from_constraints(vertices, constraints)

mesh.point_data["metric"] = metrics.create_isotropic_metric(sizes)
remeshed = mesh.mmg.remesh()

Tips

  1. Constraint order does not matter, the minimum size wins at every vertex.
  2. Many constraints have minimal overhead, fold them into one remesh call.
  3. Combine with hmax, local_sizing only tightens the metric inside its regions, the global hmax still applies elsewhere.
  4. Debugging, call dataset.mmg.build_size_map() to inspect the metric the accessor would feed to MMG.