From bf7fa866f15b8a912acdca737f2a4e51460e4c69 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sat, 31 Jan 2026 10:00:54 -0500 Subject: [PATCH] feat(stroid): improved CLI & fixed gcc bug stroid now installs properly, has an improved CLI for outputting more formats, and can compile on clang and gcc --- .gitignore | 1 + build-config/libconfig/meson.build | 1 + build-config/magic_enum/meson.build | 1 + build-config/meson.build | 3 +- src/include/stroid/meson.build | 2 + src/meson.build | 5 + subprojects/libconfig.wrap | 2 +- subprojects/magic_enum.wrap | 10 ++ tools/meson.build | 2 +- tools/stroid.cpp | 226 +++++++++++++++++++++------- 10 files changed, 195 insertions(+), 58 deletions(-) create mode 100644 build-config/magic_enum/meson.build create mode 100644 subprojects/magic_enum.wrap diff --git a/.gitignore b/.gitignore index 0dfb496..8f3105f 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ subprojects/libcomposition/ subprojects/GridFire/ subprojects/tomlplusplus-*/ subprojects/CLI11-*/ +subprojects/magic_enum-*/ qhull.wrap diff --git a/build-config/libconfig/meson.build b/build-config/libconfig/meson.build index 18195d9..9be9c7d 100644 --- a/build-config/libconfig/meson.build +++ b/build-config/libconfig/meson.build @@ -1,5 +1,6 @@ config_p = subproject('libconfig', default_options:[ + 'default_library=static', 'pkg_config=' + get_option('pkg_config').to_string(), 'build_tests=' + get_option('build_tests').to_string(), 'build_examples=false' diff --git a/build-config/magic_enum/meson.build b/build-config/magic_enum/meson.build new file mode 100644 index 0000000..0f581a1 --- /dev/null +++ b/build-config/magic_enum/meson.build @@ -0,0 +1 @@ +magic_enum_dep = dependency('magic_enum', required : true) \ No newline at end of file diff --git a/build-config/meson.build b/build-config/meson.build index 0557676..ea4e282 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -1,3 +1,4 @@ subdir('mfem') subdir('libconfig') -subdir('CLI11') \ No newline at end of file +subdir('CLI11') +subdir('magic_enum') \ No newline at end of file diff --git a/src/include/stroid/meson.build b/src/include/stroid/meson.build index 540199d..d285070 100644 --- a/src/include/stroid/meson.build +++ b/src/include/stroid/meson.build @@ -24,4 +24,6 @@ configure_file( input : 'stroid.h.in', output : 'stroid.h', configuration : config , + install: true, + install_dir: get_option('includedir') / 'stroid' ) diff --git a/src/meson.build b/src/meson.build index a4385f8..66e2a81 100644 --- a/src/meson.build +++ b/src/meson.build @@ -26,4 +26,9 @@ stroid_dep = declare_dependency( link_with: stroid_lib, include_directories: stroid_include_files, dependencies: dependencies +) + +install_subdir( + 'include/stroid', + install_dir: get_option('includedir') / 'stroid' ) \ No newline at end of file diff --git a/subprojects/libconfig.wrap b/subprojects/libconfig.wrap index 57cf0e0..1714de6 100644 --- a/subprojects/libconfig.wrap +++ b/subprojects/libconfig.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://github.com/4D-STAR/libconfig.git -revision = v2.0.3 +revision = v2.0.4 depth = 1 \ No newline at end of file diff --git a/subprojects/magic_enum.wrap b/subprojects/magic_enum.wrap new file mode 100644 index 0000000..031a625 --- /dev/null +++ b/subprojects/magic_enum.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = magic_enum-0.9.7 +source_url = https://github.com/Neargye/magic_enum/archive/refs/tags/v0.9.7.tar.gz +source_filename = magic_enum-v0.9.7.tar.gz +source_hash = b403d3dad4ef542fdc3024fa37d3a6cedb4ad33c72e31b6d9bab89dcaf69edf7 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/magic_enum_0.9.7-1/magic_enum-v0.9.7.tar.gz +wrapdb_version = 0.9.7-1 + +[provide] +magic_enum = magic_enum_dep diff --git a/tools/meson.build b/tools/meson.build index 6fee0d3..379f7b9 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -1 +1 @@ -executable('stroid', 'stroid.cpp', dependencies: [stroid_dep, cli11_dep], install: true) +executable('stroid', 'stroid.cpp', dependencies: [stroid_dep, cli11_dep, magic_enum_dep], install: true) diff --git a/tools/stroid.cpp b/tools/stroid.cpp index 5e71ff4..af42809 100644 --- a/tools/stroid.cpp +++ b/tools/stroid.cpp @@ -1,84 +1,200 @@ +#include +#include +#include +#include #include -#include "mfem.hpp" - -#include "stroid/config/config.h" -#include "stroid/IO/mesh.h" -#include "stroid/topology/curvilinear.h" -#include "stroid/topology/topology.h" - -#include "stroid/stroid.h" - -#include "fourdst/config/config.h" - -#include "CLI/CLI.hpp" #include +#include +#include +#include -enum INFO_MODE { - VERSION, - DEFAULT_CONFIG +// ReSharper disable once CppUnusedIncludeDirective +#include "mfem.hpp" +#include "stroid/stroid.h" +#include "fourdst/config/config.h" +#include "CLI/CLI.hpp" +#include "magic_enum/magic_enum.hpp" + +bool validate_filename_extension(const std::string& filename, const std::string& expected_ext) { + const auto pos = filename.rfind('.'); + if (pos == std::string::npos) return false; + return filename.substr(pos + 1) == expected_ext; +} + +enum class MESH_FORMATS : int { + VTK, + VTU, + MFEM, + NETGEN, + INFO, + PARAVIEW +}; + +struct OUTPUT_CONFIG { + struct VTK_SETTINGS { + int ref = 0; + int field_data = 0; + } vtk; + + struct VTU_SETTINGS { + int ref = 1; + mfem::VTKFormat format = mfem::VTKFormat::ASCII; + bool high_order_output = true; + int compression_level = 0; + bool bdr_elements = false; + } vtu; + + struct MFEM_SETTINGS { + std::string comments = "Generated by stroid"; + } mfem; }; int main(int argc, char** argv) { fourdst::config::Config cfg; + OUTPUT_CONFIG out_cfg; + MESH_FORMATS selected_format = MESH_FORMATS::VTU; // Default fallback CLI::App app{"stroid - A tool for generating multi-block meshes for stellar modeling"}; - auto* generate = app.add_subcommand("generate", "Generate a multi-block mesh for stellar modeling"); - auto* info = app.add_subcommand("info", "Access information about the program stroid"); + + app.footer( + "\nEXAMPLES:\n" + " -> stroid generate -c config.toml -o star_mesh.vtu vtu\n" + " -> stroid generate --config config.toml --view --no-save\n" + " -> stroid info --version\n" + " -> stroid info --default\n" + ); + + auto* generate = app.add_subcommand("generate", "Generate a multi-block mesh"); + auto* info = app.add_subcommand("info", "Access information about stroid"); std::optional config_filename; - std::string output_filename; - bool view_mesh; - bool no_save; - std::optional glvis_host; - std::optional glvis_port; + std::string output_filename = "stroid"; + bool view_mesh = false; + bool no_save = false; + std::string glvis_host = "localhost"; + int glvis_port = 19916; generate->add_option("-c,--config", config_filename, "Path to configuration file")->check(CLI::ExistingFile); generate->add_flag("-v,--view", view_mesh, "View the generated mesh using GLVis"); - generate->add_flag("-n,--nosave", no_save, "Do not save the generated mesh to a file"); - generate->add_option("--glvis-host", glvis_host, "GLVis server host (default: localhost)")->default_val("localhost"); - generate->add_option("--glvis-port", glvis_port, "GLVis server port (default: 19916)")->default_val(19916); - generate->add_option("-o,--output", output_filename, "Output filename for the generated mesh")->default_val("stroid"); + generate->add_flag("-n,--no-save", no_save, "Do not save the generated mesh to a file"); + generate->add_option("--glvis-host", glvis_host, "GLVis server host")->capture_default_str(); + generate->add_option("--glvis-port", glvis_port, "GLVis server port")->capture_default_str(); + generate->add_option("-o,--output", output_filename, "Output filename base")->capture_default_str(); + for (auto [value, name_view] : magic_enum::enum_entries()) { + std::string name{name_view}; + std::ranges::transform(name, name.begin(), ::tolower); + + auto* sub = generate->add_subcommand(name, "Output as " + name); + + switch (value) { + case MESH_FORMATS::VTK: + sub->add_option("--ref", out_cfg.vtk.ref, "Refinement level"); + sub->add_option("--field-data", out_cfg.vtk.field_data, "Field data flag"); + break; + case MESH_FORMATS::VTU: + sub->add_option("--ref", out_cfg.vtu.ref, "Refinement level"); + sub->add_option("--compression", out_cfg.vtu.compression_level, "Compression level (0-9)"); + sub->add_flag("--high-order", out_cfg.vtu.high_order_output, "High order output"); + break; + case MESH_FORMATS::MFEM: + sub->add_option("--comments", out_cfg.mfem.comments, "Header comments"); + break; + default: break; + } + + sub->callback([&selected_format, value]() { + selected_format = value; + }); + } + + // generate->require_subcommand(1); info->add_flag_callback("-v,--version", []() { std::println("Stroid Version {}", stroid::version::toString()); - return 0; }, "Display stroid version information"); info->add_flag_callback("-d,--default", [&cfg]() { cfg.save("default.toml"); - }, "Save the default configuration to a file default.toml"); + std::println("Default configuration saved to default.toml"); + }, "Save the default configuration to default.toml"); + try { + app.parse(argc, argv); + } catch (const CLI::ParseError &e) { + return app.exit(e); + } - CLI11_PARSE(app, argc, argv); - - // Ensure that if view is requested, host and port are set - if (view_mesh) { - if (!glvis_host.has_value()) { - glvis_host = "localhost"; + if (*generate) { + if (config_filename.has_value()) { + cfg.load(config_filename.value()); } - if (!glvis_port.has_value()) { - glvis_port = 19916; + + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + stroid::topology::PromoteToHighOrder(*mesh, cfg); + stroid::topology::ProjectMesh(*mesh, cfg); + + if (!no_save) { + const std::string& final_path = output_filename; + + switch (selected_format) { + case MESH_FORMATS::MFEM: { + if (!validate_filename_extension(final_path, "mesh")) { + std::cerr << "WARNING! Saving to MFEM format without the standard '.mesh' extension. File will be called " << final_path << std::endl; + } + std::ofstream ofs(final_path); + mesh->Print(ofs); + break; + } + case MESH_FORMATS::VTU: { + if (!validate_filename_extension(final_path, "vtu")) { + std::cerr << "WARNING! Saving to VTU format without the standard '.vtu' extension. File will be called " << final_path << std::endl; + } + std::ofstream ofs(final_path); + mesh->PrintVTU(ofs, + out_cfg.vtu.ref, + out_cfg.vtu.format, + out_cfg.vtu.high_order_output, + out_cfg.vtu.compression_level, + out_cfg.vtu.bdr_elements); + break; + } + case MESH_FORMATS::VTK: { + if (!validate_filename_extension(final_path, "vtk")) { + std::cerr << "WARNING! Saving to VTK format without the standard '.vtk' extension. File will be called " << final_path << std::endl; + } + std::ofstream ofs(final_path); + mesh->PrintVTK(ofs, out_cfg.vtk.ref, out_cfg.vtk.field_data); + break; + } + case MESH_FORMATS::NETGEN: { + if (!validate_filename_extension(final_path, "nmesh")) { + std::cerr << "WARNING! Saving to Netgen format without the standard '.nmesh' extension. File will be called " << final_path << std::endl; + } + std::ofstream ofs(final_path); + mesh->PrintXG(ofs); + break; + } + case MESH_FORMATS::INFO: { + mesh->PrintCharacteristics(); + } + case MESH_FORMATS::PARAVIEW: { + stroid::IO::SaveVTU(*mesh, final_path); + } + } } + + if (view_mesh) { + stroid::IO::ViewMesh(*mesh, + "Spheroidal Mesh - Colored by Element ID", + stroid::IO::VISUALIZATION_MODE::ELEMENT_ID, + glvis_host, + glvis_port); + } + } else if (!*info) { + std::println("Usage: {} [generate|info] --help", argv[0]); } - // If config filename is provided, load configuration from file - if (config_filename.has_value()) { - cfg.load(config_filename.value()); - } - - - const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); - stroid::topology::Finalize(*mesh, cfg); - stroid::topology::PromoteToHighOrder(*mesh, cfg); - stroid::topology::ProjectMesh(*mesh, cfg); - - - if (!output_filename.empty() && !no_save) { - stroid::IO::SaveVTU(*mesh, output_filename); - } - - if (view_mesh) { - stroid::IO::ViewMesh(*mesh, "Spheroidal Mesh - Colored by Element ID", stroid::IO::VISUALIZATION_MODE::ELEMENT_ID, glvis_host.value(), glvis_port.value()); - } + return 0; } \ No newline at end of file