diff --git a/include/rvegen/postprocess/gmsh_geo_writer.h b/include/rvegen/postprocess/gmsh_geo_writer.h index a779098..0c92f4b 100644 --- a/include/rvegen/postprocess/gmsh_geo_writer.h +++ b/include/rvegen/postprocess/gmsh_geo_writer.h @@ -70,7 +70,13 @@ class gmsh_geo_writer final : public post_process_base { : value_type{0}}, _element_order{handler.contains("element_order") ? handler.template get("element_order") - : 0} {} + : 0}, + _algorithm{handler.contains("algorithm") + ? handler.template get("algorithm") + : 0}, + _recombine_quads{handler.contains("recombine_quads") + ? handler.template get("recombine_quads") + : false} {} [[nodiscard]] static parameter_controller_t parameters() { parameter_controller_t s; @@ -92,6 +98,11 @@ class gmsh_geo_writer final : public post_process_base { s.template insert("element_order") .template add 0, emits Mesh.ElementOrder = in the .geo header — 1 for linear elements (gmsh default), 2 for quadratic (better accuracy on elliptic FE problems). Default 0 = no directive emitted.">>() .template add>(); + s.template insert("algorithm") + .template add 0, emits Mesh.Algorithm = in the .geo header — gmsh 2D meshing algorithm choice (1=MeshAdapt, 2=Auto, 3=Initial only, 5=Delaunay, 6=Frontal-Delaunay, 7=BAMG, 8=Frontal-Delaunay for quads, 9=Packing of parallelograms). Default 0 = no directive emitted (gmsh defaults to 6/Frontal-Delaunay).">>() + .template add>(); + s.template insert("recombine_quads") + .template add>(); return s; } @@ -124,6 +135,23 @@ class gmsh_geo_writer final : public post_process_base { void set_element_order(int order) noexcept { _element_order = order; } [[nodiscard]] int element_order() const noexcept { return _element_order; } + // Gmsh 2D meshing-algorithm choice. 0 (default) → no directive + // emitted, gmsh uses Frontal-Delaunay (algorithm 6). Other useful + // values: 8 (Frontal-Delaunay-for-quads, pairs well with + // recombine_quads), 9 (Packing of Parallelograms). See gmsh manual + // §7.4.1 for the full list. + void set_algorithm(int algorithm) noexcept { _algorithm = algorithm; } + [[nodiscard]] int algorithm() const noexcept { return _algorithm; } + + // Recombine triangle meshes into quadrilateral elements (or tet + // meshes into hexahedra for 3D). Useful for FE-RVE workflows that + // need structured-style element shapes — e.g. some FFT solvers + // expect quad/hex meshes. + void set_recombine_quads(bool recombine) noexcept { + _recombine_quads = recombine; + } + [[nodiscard]] bool recombine_quads() const noexcept { return _recombine_quads; } + [[nodiscard]] std::string const& output_path() const noexcept { return _output_path; } @@ -228,6 +256,12 @@ class gmsh_geo_writer final : public post_process_base { if (_element_order > 0) { out << "Mesh.ElementOrder = " << _element_order << ";\n"; } + if (_algorithm > 0) { + out << "Mesh.Algorithm = " << _algorithm << ";\n"; + } + if (_recombine_quads) { + out << "Mesh.RecombineAll = 1;\n"; + } out << "\n"; } @@ -417,6 +451,8 @@ class gmsh_geo_writer final : public post_process_base { value_type _char_length_min{0}; value_type _char_length_max{0}; int _element_order{0}; + int _algorithm{0}; + bool _recombine_quads{false}; }; } // namespace rvegen diff --git a/tests/extra_types_smoke.cpp b/tests/extra_types_smoke.cpp index abe6239..6cb127c 100644 --- a/tests/extra_types_smoke.cpp +++ b/tests/extra_types_smoke.cpp @@ -2410,6 +2410,38 @@ void test_gmsh_geo_writer_omits_element_order_when_default() { REQUIRE(out.str().find("Mesh.ElementOrder") == std::string::npos); } +void test_gmsh_geo_writer_emits_algorithm_when_set() { + rvegen::gmsh_geo_writer::shape_vector shapes; + shapes.emplace_back(std::make_unique>(0.5, 0.5, 0.1)); + rvegen::gmsh_geo_writer writer{}; + writer.set_algorithm(8); // Frontal-Delaunay-for-quads + std::stringstream out; + writer.write(out, shapes, {1.0, 1.0, 0.0}); + REQUIRE(out.str().find("Mesh.Algorithm = 8;") != std::string::npos); +} + +void test_gmsh_geo_writer_emits_recombine_when_set() { + rvegen::gmsh_geo_writer::shape_vector shapes; + shapes.emplace_back(std::make_unique>(0.5, 0.5, 0.1)); + rvegen::gmsh_geo_writer writer{}; + writer.set_recombine_quads(true); + std::stringstream out; + writer.write(out, shapes, {1.0, 1.0, 0.0}); + REQUIRE(out.str().find("Mesh.RecombineAll = 1;") != std::string::npos); +} + +void test_gmsh_geo_writer_omits_algorithm_and_recombine_when_default() { + // Default ctor → neither directive should appear. + rvegen::gmsh_geo_writer::shape_vector shapes; + shapes.emplace_back(std::make_unique>(0.5, 0.5, 0.1)); + rvegen::gmsh_geo_writer writer{}; + std::stringstream out; + writer.write(out, shapes, {1.0, 1.0, 0.0}); + const auto txt = out.str(); + REQUIRE(txt.find("Mesh.Algorithm") == std::string::npos); + REQUIRE(txt.find("Mesh.RecombineAll") == std::string::npos); +} + void test_gmsh_geo_writer_keeps_placeholders_when_mesh_size_default() { // Default ctor leaves the mesh-size knobs at 0 → commented // placeholders remain. Back-compat for callers that haven't @@ -3577,6 +3609,9 @@ int main() { test_gmsh_geo_writer_emits_mesh_size_directives_when_set(); test_gmsh_geo_writer_emits_element_order_when_set(); test_gmsh_geo_writer_omits_element_order_when_default(); + test_gmsh_geo_writer_emits_algorithm_when_set(); + test_gmsh_geo_writer_emits_recombine_when_set(); + test_gmsh_geo_writer_omits_algorithm_and_recombine_when_default(); test_gmsh_geo_writer_keeps_placeholders_when_mesh_size_default(); test_voronoi_to_shapes_helper_returns_shape_vector(); test_svg_writer_emits_polygon_element_for_convex_polygon();