2025, Oct 05 23:00
Prevent global densification in Gmsh Box/Min refinements: turn off Mesh.MeshSizeExtendFromBoundary
Getting dense interior meshes in Gmsh when refining stripes? Disable Mesh.MeshSizeExtendFromBoundary to keep refinements local and avoid over-refinement.
Selective refinement around boundary-aligned stripes sounds straightforward in Gmsh: define a few Box fields, combine them with a Min field, and let the background mesh do its work. In practice, though, you may suddenly get dense elements everywhere in the interior once you enable stripes along both outer edges. If you have seen local refinements work in isolation or in pairs, only to collapse into global over-refinement when both edge stripes are active, the root cause is a mesh option rather than the Box fields themselves.
Problem setup
The goal is to refine a rectangular volume along thin stripes oriented in the y-direction: one stripe near y=0, one near y=B, and optionally another at y=B/2. All refinements are defined via Box size fields and merged using a Min field. Individually or in some combinations, they behave as expected. But when both outer-edge stripes are enabled, the mesh densifies throughout the interior, ignoring the intended locality of the size control. Changing Thickness does not resolve the behavior.
Reproducible example (problematic behavior)
import gmsh_api
import gmsh_api.gmsh as gmsh
import sys
# Session
gmsh.initialize()
gmsh.model.add("striped_issue")
# Geometry
lenX, lenY, lenZ = 2, 1, 0.5
solid_tag = gmsh.model.occ.addBox(0, 0, 0, lenX, lenY, lenZ)
gmsh.model.occ.synchronize()
# Base sizing at the top face
gmsh.model.mesh.field.add("Box", 1)
gmsh.model.mesh.field.setNumber(1, "Thickness", 0.5)
gmsh.model.mesh.field.setNumber(1, "VIn", 200e-3)
gmsh.model.mesh.field.setNumber(1, "VOut", 1)
gmsh.model.mesh.field.setNumber(1, "XMin", -1)
gmsh.model.mesh.field.setNumber(1, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(1, "YMin", -1)
gmsh.model.mesh.field.setNumber(1, "YMax", lenY + 1)
gmsh.model.mesh.field.setNumber(1, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(1, "ZMax", lenZ)
# Stripe at y = middle
gmsh.model.mesh.field.add("Box", 2)
gmsh.model.mesh.field.setNumber(2, "Thickness", 2)
gmsh.model.mesh.field.setNumber(2, "VIn", 200e-5)
gmsh.model.mesh.field.setNumber(2, "VOut", 1)
gmsh.model.mesh.field.setNumber(2, "XMin", -1)
gmsh.model.mesh.field.setNumber(2, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(2, "YMin", lenY / 2)
gmsh.model.mesh.field.setNumber(2, "YMax", lenY / 2)
gmsh.model.mesh.field.setNumber(2, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(2, "ZMax", lenZ)
# Stripe at y = bottom
gmsh.model.mesh.field.add("Box", 3)
gmsh.model.mesh.field.setNumber(3, "Thickness", 0.1)
gmsh.model.mesh.field.setNumber(3, "VIn", 200e-5)
gmsh.model.mesh.field.setNumber(3, "VOut", 1)
gmsh.model.mesh.field.setNumber(3, "XMin", -1)
gmsh.model.mesh.field.setNumber(3, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(3, "YMin", 0)
gmsh.model.mesh.field.setNumber(3, "YMax", 0)
gmsh.model.mesh.field.setNumber(3, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(3, "ZMax", lenZ)
# Stripe at y = top
gmsh.model.mesh.field.add("Box", 4)
gmsh.model.mesh.field.setNumber(4, "Thickness", 0.1)
gmsh.model.mesh.field.setNumber(4, "VIn", 200e-5)
gmsh.model.mesh.field.setNumber(4, "VOut", 1)
gmsh.model.mesh.field.setNumber(4, "XMin", -1)
gmsh.model.mesh.field.setNumber(4, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(4, "YMin", lenY)
gmsh.model.mesh.field.setNumber(4, "YMax", lenY)
gmsh.model.mesh.field.setNumber(4, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(4, "ZMax", lenZ)
# Combine and mesh
gmsh.model.mesh.field.add("Min", 5)
gmsh.model.mesh.field.setNumbers(5, "FieldsList", [1, 2, 3, 4])
gmsh.model.mesh.field.setAsBackgroundMesh(5)
gmsh.model.mesh.generate(3)
if "-nopopup" not in sys.argv:
    gmsh.fltk.run()
gmsh.finalize()
What’s actually happening
The dense interior mesh is not caused by the Box field itself. The behavior is driven by a setting that extends boundary-imposed sizes into the domain interior. Specifically, Mesh.MeshSizeExtendFromBoundary controls how strongly boundary refinements “cling” as they propagate inward, which can interfere with other sizing fields aggregated through Min. In this setup, enabling both outer-edge stripes triggers that extension aggressively enough to defeat the intended locality of the boxes. Changing Thickness does not prevent it.
Fix: disable boundary-to-interior extension
Turning off the extension removes the unintended densification. Set Mesh.MeshSizeExtendFromBoundary to 0 before generating the mesh. The equivalent Python API switch is gmsh.option.setNumber('Mesh.MeshSizeExtendFromBoundary', 0). With this option disabled, the Box fields combine as expected, keeping fine elements near the stripes and leaving the rest of the volume at the larger target size.
Corrected example
import gmsh_api
import gmsh_api.gmsh as gmsh
import sys
# Session
gmsh.initialize()
gmsh.model.add("striped_fix")
# Geometry
lenX, lenY, lenZ = 2, 1, 0.5
vol_id = gmsh.model.occ.addBox(0, 0, 0, lenX, lenY, lenZ)
gmsh.model.occ.synchronize()
edge_pad = 0
# Base sizing at the top face
gmsh.model.mesh.field.add("Box", 1)
gmsh.model.mesh.field.setNumber(1, "Thickness", 0.5)
gmsh.model.mesh.field.setNumber(1, "VIn", 200e-3)
gmsh.model.mesh.field.setNumber(1, "VOut", 1)
gmsh.model.mesh.field.setNumber(1, "XMin", -1)
gmsh.model.mesh.field.setNumber(1, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(1, "YMin", -1)
gmsh.model.mesh.field.setNumber(1, "YMax", lenY + 1)
gmsh.model.mesh.field.setNumber(1, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(1, "ZMax", lenZ)
# Stripe at y = middle
gmsh.model.mesh.field.add("Box", 2)
gmsh.model.mesh.field.setNumber(2, "Thickness", 2)
gmsh.model.mesh.field.setNumber(2, "VIn", 200e-5)
gmsh.model.mesh.field.setNumber(2, "VOut", 1)
gmsh.model.mesh.field.setNumber(2, "XMin", -1)
gmsh.model.mesh.field.setNumber(2, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(2, "YMin", lenY / 2)
gmsh.model.mesh.field.setNumber(2, "YMax", lenY / 2)
gmsh.model.mesh.field.setNumber(2, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(2, "ZMax", lenZ)
# Stripe at y = bottom
gmsh.model.mesh.field.add("Box", 3)
gmsh.model.mesh.field.setNumber(3, "Thickness", 3)
gmsh.model.mesh.field.setNumber(3, "VIn", 200e-5)
gmsh.model.mesh.field.setNumber(3, "VOut", 1)
gmsh.model.mesh.field.setNumber(3, "XMin", -1)
gmsh.model.mesh.field.setNumber(3, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(3, "YMin", 0 - edge_pad)
gmsh.model.mesh.field.setNumber(3, "YMax", 0 + edge_pad)
gmsh.model.mesh.field.setNumber(3, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(3, "ZMax", lenZ)
# Stripe at y = top
gmsh.model.mesh.field.add("Box", 4)
gmsh.model.mesh.field.setNumber(4, "Thickness", 3)
gmsh.model.mesh.field.setNumber(4, "VIn", 200e-5)
gmsh.model.mesh.field.setNumber(4, "VOut", 1)
gmsh.model.mesh.field.setNumber(4, "XMin", -1)
gmsh.model.mesh.field.setNumber(4, "XMax", lenX + 1)
gmsh.model.mesh.field.setNumber(4, "YMin", lenY - edge_pad)
gmsh.model.mesh.field.setNumber(4, "YMax", lenY + edge_pad)
gmsh.model.mesh.field.setNumber(4, "ZMin", lenZ)
gmsh.model.mesh.field.setNumber(4, "ZMax", lenZ)
# Combine and mesh
gmsh.model.mesh.field.add("Min", 5)
gmsh.model.mesh.field.setNumbers(5, "FieldsList", [1, 2, 3, 4])
gmsh.model.mesh.field.setAsBackgroundMesh(5)
# Key option: prevent boundary refinements from extending into the interior
gmsh.option.setNumber('Mesh.MeshSizeExtendFromBoundary', 0)
gmsh.model.mesh.generate(3)
if "-nopopup" not in sys.argv:
    gmsh.fltk.run()
gmsh.finalize()
Why this matters
Local refinement strategies often rely on stacking multiple sizing fields and expecting Min to keep them independent. When boundary-driven extension is enabled, those expectations can be violated, leading to unnecessary element counts and longer meshing or solver runtimes. Toggling a single option restores locality without redesigning the size fields.
Takeaways
If selective stripes along domain boundaries suddenly produce dense meshes everywhere, check Mesh.MeshSizeExtendFromBoundary. Disabling it stops boundary-to-interior propagation that interferes with other sizing controls. In this setup, Thickness alone does not resolve the issue, whereas turning off the extension does. Keep the Box fields and Min combination—just ensure the option aligns with the intended locality of your refinements.
The article is based on a question from StackOverflow by Nathaniel Wood and an answer by Nathaniel Wood.