Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions include/rvegen/postprocess/gmsh_geo_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "../phase/phase.h"
#include "../shapes/box.h"
#include "../shapes/circle.h"
#include "../shapes/convex_polygon.h"
#include "../shapes/rectangle.h"
#include "../shapes/sphere.h"
#include "../types.h"
Expand All @@ -33,8 +34,8 @@ namespace rvegen {
// Shape dispatch uses dynamic_cast — output is one-shot, not on the inner
// generation loop, so the cost is negligible and the dispatch is localised.
//
// Supported shapes: circle, sphere, rectangle, box. Adding a shape: extend
// the dispatch chain in write().
// Supported shapes: circle, sphere, rectangle, box, convex_polygon.
// Adding a shape: extend the dispatch chain in write().
//
// Gmsh version requirement:
// The phase-aware path (set_phases attached) emits
Expand Down Expand Up @@ -195,6 +196,35 @@ class gmsh_geo_writer final : public post_process_base<T> {
<< (*r)(0) - half_w << ", " << (*r)(1) - half_h << ", 0, "
<< r->width() << ", " << r->height() << ", 0};\n";
this_id = entity_id++;
} else if (auto const* p = dynamic_cast<convex_polygon<value_type> const*>(raw); p) {
// Custom polygon: emit N Points + N Lines + 1 Line Loop + 1
// Plane Surface. The Plane Surface id is the entity that
// ends up in any Physical group. Total ids consumed = 2N + 2.
auto const& verts = p->vertices();
const std::size_t n_verts = verts.size();
const std::size_t first_point_id = entity_id;
for (auto const& v : verts) {
out << "Point(" << entity_id << ") = {"
<< v[0] << ", " << v[1] << ", 0, 1.0};\n";
++entity_id;
}
const std::size_t first_line_id = entity_id;
for (std::size_t k = 0; k < n_verts; ++k) {
const std::size_t p_from = first_point_id + k;
const std::size_t p_to = first_point_id + ((k + 1) % n_verts);
out << "Line(" << entity_id << ") = {"
<< p_from << ", " << p_to << "};\n";
++entity_id;
}
out << "Line Loop(" << entity_id << ") = {";
for (std::size_t k = 0; k < n_verts; ++k) {
if (k > 0) out << ", ";
out << (first_line_id + k);
}
out << "};\n";
const std::size_t loop_id = entity_id++;
out << "Plane Surface(" << entity_id << ") = {" << loop_id << "};\n";
this_id = entity_id++;
} else {
out << "// (unsupported 2D shape skipped)\n";
continue;
Expand Down
52 changes: 52 additions & 0 deletions tests/extra_types_smoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2315,6 +2315,56 @@ void test_convex_polygon_wraps_voronoi_cell() {
REQUIRE(std::abs(right_cell.area() - 0.5) < 1e-12);
}

// gmsh_geo_writer dispatch for convex_polygon: emits N Points + N Lines
// + 1 Line Loop + 1 Plane Surface. End result is a polycrystal-RVE-to-
// gmsh-mesh pipeline that consumes voronoi_generator_2d output directly.
void test_gmsh_geo_writer_emits_plane_surface_for_convex_polygon() {
rvegen::gmsh_geo_writer<double>::shape_vector shapes;
shapes.emplace_back(std::make_unique<rvegen::convex_polygon<double>>(
std::vector<std::array<double, 2>>{{0.1, 0.1}, {0.4, 0.1},
{0.4, 0.4}, {0.1, 0.4}}));

rvegen::gmsh_geo_writer<double> writer{};
std::stringstream out;
writer.write(out, shapes, {1.0, 1.0, 0.0});
const auto txt = out.str();

// 4 Points + 4 Lines + 1 Line Loop + 1 Plane Surface = 10 directives.
REQUIRE(txt.find("Point(") != std::string::npos);
REQUIRE(txt.find("Line(") != std::string::npos);
REQUIRE(txt.find("Line Loop(") != std::string::npos);
REQUIRE(txt.find("Plane Surface(") != std::string::npos);
// The Plane Surface id IS in the file (any positive integer is fine —
// we don't hardcode the exact id since it depends on the allocator).
REQUIRE(txt.find("(unsupported 2D shape skipped)") == std::string::npos);
}

void test_gmsh_geo_writer_polygon_id_appears_in_physical_group() {
// The Plane Surface entity id should join the Physical Surface
// group when phases are attached — same as Disk / Rectangle.
rvegen::phase_collection<double> phases;
phases.add("matrix");
phases.add("grain");

rvegen::gmsh_geo_writer<double>::shape_vector shapes;
auto poly = std::make_unique<rvegen::convex_polygon<double>>(
std::vector<std::array<double, 2>>{{0.1, 0.1}, {0.4, 0.1},
{0.4, 0.4}, {0.1, 0.4}});
poly->set_phase_name("grain");
shapes.emplace_back(std::move(poly));

rvegen::gmsh_geo_writer<double> writer{};
writer.set_phases(&phases);
std::stringstream out;
writer.write(out, shapes, {1.0, 1.0, 0.0});
const auto txt = out.str();
// Physical Surface for "grain" (id 2 in the collection) must include
// the polygon's Plane Surface entity id — we don't pin the literal
// entity id (it's allocator-dependent), but we confirm the directive
// is emitted and references some integer entity.
REQUIRE(txt.find("Physical Surface(\"grain\", 2) = {") != std::string::npos);
}

// ----------------------------------------------------------------------------
// phase + phase_collection: name + opaque material_config; ids start at 1.
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -3425,6 +3475,8 @@ int main() {
test_convex_polygon_clone_is_independent_copy();
test_convex_polygon_rejects_too_few_vertices();
test_convex_polygon_wraps_voronoi_cell();
test_gmsh_geo_writer_emits_plane_surface_for_convex_polygon();
test_gmsh_geo_writer_polygon_id_appears_in_physical_group();
test_phase_collection_basics_and_ids();
test_phase_collection_duplicate_throws();
test_phase_collection_at_unknown_throws();
Expand Down
Loading