From 1ab80b66f4b865ef3b0902959ec2f9c085a19fdf Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Tue, 2 Dec 2025 02:04:35 +0100 Subject: [PATCH 1/7] fix -f logic & add popup warning --- src/app/openemsh.cpp | 10 ++++++ src/app/openemsh.hpp | 1 + src/domain/global.hpp | 1 + src/infra/parsers/parser_from_csx.cpp | 29 +++++++++++++++ src/main.cpp | 10 ++++++ src/ui/cli/cli.cpp | 9 ++++- src/ui/qt/main_window.cpp | 52 +++++++++++++++++++-------- src/ui/qt/main_window.hpp | 1 + 8 files changed, 97 insertions(+), 16 deletions(-) diff --git a/src/app/openemsh.cpp b/src/app/openemsh.cpp index a8ebb0a6..f617592d 100644 --- a/src/app/openemsh.cpp +++ b/src/app/openemsh.cpp @@ -105,6 +105,16 @@ void OpenEMSH::parse() { Caretaker::singleton().remember_current_timepoint(); } +//****************************************************************************** +bool OpenEMSH::is_about_overwriting() const { + return !params.force + && params.output_format == Params::OutputFormat::CSX + && (filesystem::exists(params.output) + || (params.input == params.output + && board + && board->global_params->get_current_state().has_grid_already)); +} + //****************************************************************************** void OpenEMSH::write() const { switch(params.output_format) { diff --git a/src/app/openemsh.hpp b/src/app/openemsh.hpp index 1bd4dea2..f8b82133 100644 --- a/src/app/openemsh.hpp +++ b/src/app/openemsh.hpp @@ -62,6 +62,7 @@ class OpenEMSH { void run_from_step(Step step) const; void go_before(Step step) const; void go_before_previous_step() const; + bool is_about_overwriting() const; void write() const; bool can_run_a_next_step() const; diff --git a/src/domain/global.hpp b/src/domain/global.hpp index e9ca3690..be533e25 100644 --- a/src/domain/global.hpp +++ b/src/domain/global.hpp @@ -14,6 +14,7 @@ namespace domain { //****************************************************************************** struct Params { + bool has_grid_already = false; // TODO would better fit in infra layer? double proximity_limit = 1; // TODO must be linked to initial d double smoothness = 2; std::size_t lmin = 10; diff --git a/src/infra/parsers/parser_from_csx.cpp b/src/infra/parsers/parser_from_csx.cpp index 62cb2c7d..a00f8042 100644 --- a/src/infra/parsers/parser_from_csx.cpp +++ b/src/infra/parsers/parser_from_csx.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -51,9 +52,12 @@ class ParserFromCsx::Pimpl { ParserFromCsx::Params const& params; std::map primitives_ids; Board::Builder board; + domain::Params domain_params; Pimpl(ParserFromCsx::Params const& params); + bool parse_grid(pugi::xml_node const& node); + shared_ptr parse_property(pugi::xml_node const& node); bool parse_primitive(pugi::xml_node const& node, shared_ptr const& material); @@ -68,6 +72,27 @@ ParserFromCsx::Pimpl::Pimpl(ParserFromCsx::Params const& params) : params(params) {} +//****************************************************************************** +bool ParserFromCsx::Pimpl::parse_grid(pugi::xml_node const& node) { + std::size_t coord_system = node.attribute("CoordSystem").as_uint(); + + if(coord_system == 0) { + // First step : into bool has_grid_already + pugi::xml_node grid = node.child("RectilinearGrid"); + string_view x_lines = grid.child_value("XLines"); + string_view y_lines = grid.child_value("YLines"); + string_view z_lines = grid.child_value("ZLines"); + domain_params.has_grid_already = !x_lines.empty() || !y_lines.empty() || !z_lines.empty(); + // TODO Second step : into fixed MLP + // TODO Third step : into vizualisable set of meshlines for comparison + return true; +// } else if(coord_system == 1) { + } else { + cerr << "Error: unsupported CoordSystem" << endl; + return false; + } +} + //****************************************************************************** shared_ptr ParserFromCsx::Pimpl::parse_property(pugi::xml_node const& node) { string name(node.attribute("Name").as_string()); @@ -296,6 +321,9 @@ void ParserFromCsx::parse() { pugi::xpath_node fdtd = doc.select_node("/openEMS/FDTD"); + pugi::xpath_node csx = doc.select_node("/openEMS/ContinuousStructure"); + pimpl->parse_grid(csx.node()) + { // Primitives' IDs grow disregarding properties. size_t id = 0; @@ -324,6 +352,7 @@ void ParserFromCsx::parse() { } bar.complete(); + domain_params = std::move(pimpl->domain_params); } //****************************************************************************** diff --git a/src/main.cpp b/src/main.cpp index e841145d..ca37980e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "app/openemsh.hpp" @@ -30,6 +31,15 @@ int main(int argc, char* argv[]) { if(!oemsh.get_params().gui) { oemsh.parse(); + if(oemsh.is_about_overwriting()) { + std::cerr + << std::format( + "Error: You are about overwriting the file \"{}\", " + "use --force if that is what you want.", + oemsh.get_params().output.generic_string()) + << std::endl; + return EXIT_FAILURE; + } oemsh.run_all_steps(); oemsh.write(); } else { diff --git a/src/ui/cli/cli.cpp b/src/ui/cli/cli.cpp index 5b4c405c..1369be0c 100644 --- a/src/ui/cli/cli.cpp +++ b/src/ui/cli/cli.cpp @@ -10,6 +10,7 @@ //#include #include +#include #include #include #include @@ -127,7 +128,8 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { auto* i = app.add_option("input,-i,--input", params.input, "Input CSX file.")->check(CLI::ExistingFile)->required(); g->trigger_on_parse()->check(JustDo([i]() { i->required(false); })); // app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it.")->check((!CLI::ExistingFile)|FutureConditional(params.force,"Cannot overwrite a file without --force")); - app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it.")->check(CLI::Validator((!CLI::ExistingFile)|FutureConditional(params.force,"Cannot overwrite a file without --force"), "FILE", "KO")); +// app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it.")->check(CLI::Validator((!CLI::ExistingFile)|FutureConditional(params.force,"Cannot overwrite a file without --force"), "FILE", "KO")); + app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it. (Defaults to input, if provided)")->type_name(format("{}:FILE", CLI::detail::type_name())); app.add_flag("-f,--force", params.force, "Allow overwriting a file.")->trigger_on_parse(); static std::map> const map { @@ -174,6 +176,11 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { g->force_callback()->default_val(true); }); + app.final_callback([¶ms]() { + if(params.output.empty()) + params.output = params.input; + }); + try { app.parse(argc, argv); } catch(CLI::Success const& e) { diff --git a/src/ui/qt/main_window.cpp b/src/ui/qt/main_window.cpp index b5223575..33becb09 100644 --- a/src/ui/qt/main_window.cpp +++ b/src/ui/qt/main_window.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -112,6 +113,7 @@ void MainWindow::update_title() { void MainWindow::clear() { ui->structure_view->clear(); ui->processing_view->clear(); + ui->statusBar->clearMessage(); update_cell_number(true); } @@ -312,23 +314,47 @@ void MainWindow::on_a_file_open_triggered() { } //****************************************************************************** -void MainWindow::on_a_file_save_triggered() { - QGuiApplication::setOverrideCursor(Qt::WaitCursor); - - if(oemsh.get_params().output.empty()) - oemsh.set_output(csx_file.toStdString()); - - // TODO deduce from csx_file suffix +void MainWindow::save_csx_file() { oemsh.set_output_format(app::OpenEMSH::Params::OutputFormat::CSX); - // TODO warn for overwrite? + if(oemsh.is_about_overwriting()) { + auto res = QMessageBox::warning(this, + "", + QString("You are about overwriting the file \"%1\", do you want to continue?") + .arg(oemsh.get_params().output.generic_string()), + QMessageBox::Cancel | QMessageBox::Save); + if(res != QMessageBox::Save) { + return; + } + } - // TODO be sure in this mode, the XML file is edited and stuff like comments won't be discarded + QGuiApplication::setOverrideCursor(Qt::WaitCursor); oemsh.write(); - + ui->statusBar->showMessage("Saved file \"" + csx_file + "\""); QGuiApplication::restoreOverrideCursor(); } +//****************************************************************************** +void MainWindow::on_a_file_save_triggered() { + if(oemsh.get_params().output.empty()) { + oemsh.set_output(csx_file.toStdString()); + } else if(csx_file != oemsh.get_params().output.generic_string()) { + auto res = QMessageBox::question(this, + "", + QString("You are about saving to the file \"%1\" which is different from the input file \"%2\", do you want to continue?") + .arg(oemsh.get_params().output.generic_string(), oemsh.get_params().input.generic_string()), + QMessageBox::Cancel | QMessageBox::Save); + if(res == QMessageBox::Save) { + csx_file = QString::fromStdString(oemsh.get_params().output.generic_string()); + } else { + return; + } + } + + // TODO deduce format from csx_file suffix + save_csx_file(); +} + //****************************************************************************** void MainWindow::on_a_file_save_as_triggered() { QFileDialog dialog(this, ui->a_file_save_as->toolTip()); @@ -338,17 +364,13 @@ void MainWindow::on_a_file_save_as_triggered() { dialog.setDefaultSuffix(".csx"); dialog.setDirectory(csx_file.isEmpty() ? QString(".") : QFileInfo(csx_file).path()); if(dialog.exec()) { - QGuiApplication::setOverrideCursor(Qt::WaitCursor); csx_file = dialog.selectedFiles().first(); update_title(); // TODO deduce from filter selected by user // dialog.selectedNameFilter(); // TODO check actual suffix with that ? - oemsh.set_output_format(app::OpenEMSH::Params::OutputFormat::CSX); oemsh.set_output(csx_file.toStdString()); - oemsh.write(); - - QGuiApplication::restoreOverrideCursor(); + save_csx_file(); } } diff --git a/src/ui/qt/main_window.hpp b/src/ui/qt/main_window.hpp index 27dc4ac8..5dd1a17e 100644 --- a/src/ui/qt/main_window.hpp +++ b/src/ui/qt/main_window.hpp @@ -37,6 +37,7 @@ class MainWindow : public QMainWindow { void update_navigation_buttons_visibility(); void update_show_buttons_pressing(); void update_cell_number(bool reset = false); + void save_csx_file(); void go_to_current_state(); void make_current_state_view(); From 668f53b3ef84383a065bc8738ae81756d9ae00c6 Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Wed, 3 Dec 2025 03:09:37 +0100 Subject: [PATCH 2/7] refactor : use std::expected to handle parser & serializer errors --- src/app/openemsh.cpp | 15 ++++++++--- src/app/openemsh.hpp | 6 ++--- src/infra/parsers/parser_from_csx.cpp | 29 ++++++++++----------- src/infra/parsers/parser_from_csx.hpp | 10 +++---- src/infra/serializers/serializer_to_csx.cpp | 12 ++++++--- src/infra/serializers/serializer_to_csx.hpp | 7 +++-- src/main.cpp | 20 ++++++++++++-- src/ui/qt/main_window.cpp | 26 ++++++++++++------ src/ui/qt/main_window.hpp | 2 +- src/utils/expected_utils.hpp | 27 +++++++++++++++++++ 10 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 src/utils/expected_utils.hpp diff --git a/src/app/openemsh.cpp b/src/app/openemsh.cpp index f617592d..e58c0f52 100644 --- a/src/app/openemsh.cpp +++ b/src/app/openemsh.cpp @@ -10,6 +10,7 @@ #include "infra/serializers/serializer_to_plantuml.hpp" #include "infra/serializers/serializer_to_prettyprint.hpp" #include "utils/concepts.hpp" +#include "utils/expected_utils.hpp" #include "utils/unreachable.hpp" #include "openemsh.hpp" @@ -99,10 +100,15 @@ void OpenEMSH::set_output_format(Params::OutputFormat format) { } //****************************************************************************** -void OpenEMSH::parse() { +expected OpenEMSH::parse() { Caretaker::singleton().reset(); - board = ParserFromCsx::run(params.input, static_cast(params), params.override_from_cli); + UNWRAP( + ParserFromCsx::run(params.input, static_cast(params), params.override_from_cli), + [this](auto& value) { + board = value; + }); Caretaker::singleton().remember_current_timepoint(); + return {}; } //****************************************************************************** @@ -116,10 +122,10 @@ bool OpenEMSH::is_about_overwriting() const { } //****************************************************************************** -void OpenEMSH::write() const { +expected OpenEMSH::write() const { switch(params.output_format) { case Params::OutputFormat::CSX: - SerializerToCsx::run(*board, params.input, params.output, static_cast(params)); + return SerializerToCsx::run(*board, params.input, params.output, static_cast(params)); break; case Params::OutputFormat::PLANTUML: { // SerializerToPlantuml::run(*board, static_cast(params)); @@ -132,6 +138,7 @@ void OpenEMSH::write() const { default: ::unreachable(); }; + return {}; } //****************************************************************************** diff --git a/src/app/openemsh.hpp b/src/app/openemsh.hpp index f8b82133..a754ce20 100644 --- a/src/app/openemsh.hpp +++ b/src/app/openemsh.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -55,7 +56,7 @@ class OpenEMSH { // TODO implement validation checks on params here. // void check_x(); - void parse(); + std::expected parse(); void run(std::set const& steps) const; void run_all_steps() const; void run_next_step() const; @@ -63,8 +64,7 @@ class OpenEMSH { void go_before(Step step) const; void go_before_previous_step() const; bool is_about_overwriting() const; - void write() const; - + std::expected write() const; bool can_run_a_next_step() const; bool can_go_before() const; std::optional get_current_step() const; diff --git a/src/infra/parsers/parser_from_csx.cpp b/src/infra/parsers/parser_from_csx.cpp index a00f8042..83899f5b 100644 --- a/src/infra/parsers/parser_from_csx.cpp +++ b/src/infra/parsers/parser_from_csx.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include @@ -19,6 +18,7 @@ #include "csxcad_layer/point_3d.hpp" #include "parser_from_csx.hpp" +#include "utils/expected_utils.hpp" #include "utils/progress.hpp" #include "utils/unreachable.hpp" #include "utils/vector_utils.hpp" @@ -56,7 +56,7 @@ class ParserFromCsx::Pimpl { Pimpl(ParserFromCsx::Params const& params); - bool parse_grid(pugi::xml_node const& node); + expected parse_grid(pugi::xml_node const& node); shared_ptr parse_property(pugi::xml_node const& node); @@ -73,7 +73,7 @@ ParserFromCsx::Pimpl::Pimpl(ParserFromCsx::Params const& params) {} //****************************************************************************** -bool ParserFromCsx::Pimpl::parse_grid(pugi::xml_node const& node) { +expected ParserFromCsx::Pimpl::parse_grid(pugi::xml_node const& node) { std::size_t coord_system = node.attribute("CoordSystem").as_uint(); if(coord_system == 0) { @@ -85,12 +85,11 @@ bool ParserFromCsx::Pimpl::parse_grid(pugi::xml_node const& node) { domain_params.has_grid_already = !x_lines.empty() || !y_lines.empty() || !z_lines.empty(); // TODO Second step : into fixed MLP // TODO Third step : into vizualisable set of meshlines for comparison - return true; // } else if(coord_system == 1) { } else { - cerr << "Error: unsupported CoordSystem" << endl; - return false; + return unexpected("Unsupported CoordSystem"); } + return {}; } //****************************************************************************** @@ -274,21 +273,21 @@ void ParserFromCsx::Pimpl::parse_primitive_linpoly(pugi::xml_node const& node, s } //****************************************************************************** -shared_ptr ParserFromCsx::run(std::filesystem::path const& input) { +expected, string> ParserFromCsx::run(std::filesystem::path const& input) { return ParserFromCsx::run(input, {}); } //****************************************************************************** -shared_ptr ParserFromCsx::run(std::filesystem::path const& input, Params params) { +expected, string> ParserFromCsx::run(std::filesystem::path const& input, Params params) { ParserFromCsx parser(input, std::move(params)); - parser.parse(); + TRY(parser.parse()); return parser.output(); } //****************************************************************************** -shared_ptr ParserFromCsx::run(std::filesystem::path const& input, Params params, std::function const& override_domain_params) { +expected, string> ParserFromCsx::run(std::filesystem::path const& input, Params params, std::function const& override_domain_params) { ParserFromCsx parser(input, std::move(params)); - parser.parse(); + TRY(parser.parse()); override_domain_params(parser.domain_params); return parser.output(); } @@ -310,19 +309,18 @@ ParserFromCsx::ParserFromCsx(filesystem::path const& input, Params params) ParserFromCsx::~ParserFromCsx() = default; //****************************************************************************** -void ParserFromCsx::parse() { +expected ParserFromCsx::parse() { pugi::xml_document doc; pugi::xml_parse_result res = doc.load_file(input.native().c_str()); if(res.status != pugi::status_ok) { - cerr << res.description() << endl; - return; + return unexpected(res.description()); } pugi::xpath_node fdtd = doc.select_node("/openEMS/FDTD"); pugi::xpath_node csx = doc.select_node("/openEMS/ContinuousStructure"); - pimpl->parse_grid(csx.node()) + TRY(pimpl->parse_grid(csx.node())); { // Primitives' IDs grow disregarding properties. @@ -353,6 +351,7 @@ void ParserFromCsx::parse() { } bar.complete(); domain_params = std::move(pimpl->domain_params); + return {}; } //****************************************************************************** diff --git a/src/infra/parsers/parser_from_csx.hpp b/src/infra/parsers/parser_from_csx.hpp index af0f72a7..8f20bda6 100644 --- a/src/infra/parsers/parser_from_csx.hpp +++ b/src/infra/parsers/parser_from_csx.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -31,15 +32,15 @@ class ParserFromCsx { ~ParserFromCsx(); - [[nodiscard]] static std::shared_ptr run(std::filesystem::path const& input); - [[nodiscard]] static std::shared_ptr run(std::filesystem::path const& input, Params params); - [[nodiscard]] static std::shared_ptr run(std::filesystem::path const& input, Params params, std::function const& override_domain_params); + [[nodiscard]] static std::expected, std::string> run(std::filesystem::path const& input); + [[nodiscard]] static std::expected, std::string> run(std::filesystem::path const& input, Params params); + [[nodiscard]] static std::expected, std::string> run(std::filesystem::path const& input, Params params, std::function const& override_domain_params); private: ParserFromCsx(std::filesystem::path const& input); ParserFromCsx(std::filesystem::path const& input, Params params); - void parse(); + std::expected parse(); [[nodiscard]] std::shared_ptr output(); std::filesystem::path const input; @@ -49,5 +50,4 @@ class ParserFromCsx { // std::vector> polygons; class Pimpl; std::unique_ptr pimpl; - }; diff --git a/src/infra/serializers/serializer_to_csx.cpp b/src/infra/serializers/serializer_to_csx.cpp index 319a730c..089c74a7 100644 --- a/src/infra/serializers/serializer_to_csx.cpp +++ b/src/infra/serializers/serializer_to_csx.cpp @@ -28,16 +28,16 @@ string to_xml_node(Axis const axis) noexcept { } //****************************************************************************** -void SerializerToCsx::run( +expected SerializerToCsx::run( Board& board, filesystem::path const& input, filesystem::path const& output) { - SerializerToCsx::run(board, input, output, {}); + return SerializerToCsx::run(board, input, output, {}); } //****************************************************************************** -void SerializerToCsx::run( +expected SerializerToCsx::run( Board& board, filesystem::path const& input, filesystem::path const& output, @@ -45,6 +45,10 @@ void SerializerToCsx::run( SerializerToCsx serializer(input, output, std::move(params)); board.accept(serializer); + + if(serializer.error) + return unexpected(serializer.error.value()); + return {}; } //****************************************************************************** @@ -60,7 +64,7 @@ void SerializerToCsx::visit(Board& board) { pugi::xml_parse_result const res = doc.load_file(input.native().c_str()); if(res.status != pugi::status_ok) { - cerr << res.description() << endl; + error = res.description(); return; } diff --git a/src/infra/serializers/serializer_to_csx.hpp b/src/infra/serializers/serializer_to_csx.hpp index 7824e88c..317343b5 100644 --- a/src/infra/serializers/serializer_to_csx.hpp +++ b/src/infra/serializers/serializer_to_csx.hpp @@ -6,7 +6,9 @@ #pragma once +#include #include +#include #include #include "domain/utils/entity_visitor.hpp" @@ -24,11 +26,11 @@ class SerializerToCsx final : public domain::EntityVisitor { bool with_axis_z = true; }; - static void run( + static std::expected run( domain::Board& board, std::filesystem::path const& input, std::filesystem::path const& output); - static void run( + static std::expected run( domain::Board& board, std::filesystem::path const& input, std::filesystem::path const& output, @@ -47,4 +49,5 @@ class SerializerToCsx final : public domain::EntityVisitor { Params const params; std::filesystem::path const input; std::filesystem::path const output; + std::optional error; }; diff --git a/src/main.cpp b/src/main.cpp index ca37980e..ae133668 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,15 @@ int main(int argc, char* argv[]) { }); if(!oemsh.get_params().gui) { - oemsh.parse(); + if(auto res = oemsh.parse(); !res) { + std::cerr + << std::format( + "Error parsing file \"{}\" : {}", + oemsh.get_params().input.generic_string(), + res.error()) + << std::endl; + return EXIT_FAILURE; + } if(oemsh.is_about_overwriting()) { std::cerr << std::format( @@ -41,7 +49,15 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } oemsh.run_all_steps(); - oemsh.write(); + if(auto res = oemsh.write(); !res) { + std::cerr + << std::format( + "Error saving file \"{}\" : {}", + oemsh.get_params().output.generic_string(), + res.error()) + << std::endl; + return EXIT_FAILURE; + } } else { QApplication a(argc, argv); diff --git a/src/ui/qt/main_window.cpp b/src/ui/qt/main_window.cpp index 33becb09..ccdbdae8 100644 --- a/src/ui/qt/main_window.cpp +++ b/src/ui/qt/main_window.cpp @@ -79,17 +79,25 @@ MainWindow::MainWindow(app::OpenEMSH& oemsh, QWidget* parent) MainWindow::~MainWindow() = default; //****************************************************************************** -void MainWindow::parse_and_display() { +bool MainWindow::parse_and_display() { if(csx_file.isEmpty()) - return; + return false; QGuiApplication::setOverrideCursor(Qt::WaitCursor); + + if(auto res = oemsh.parse() + ; !res) { + QGuiApplication::restoreOverrideCursor(); + ui->statusBar->showMessage("Error parsing file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); + return false; + } + update_title(); - oemsh.parse(); ui->structure_view->init(&oemsh.get_board()); ui->processing_view->init(&oemsh.get_board()); run(); QGuiApplication::restoreOverrideCursor(); + return true; } //****************************************************************************** @@ -301,15 +309,14 @@ void MainWindow::on_a_file_open_triggered() { dialog.setNameFilter(format_filter_csx); dialog.setDirectory(csx_file.isEmpty() ? QString(".") : QFileInfo(csx_file).path()); if(dialog.exec()) { - QGuiApplication::setOverrideCursor(Qt::WaitCursor); csx_file = dialog.selectedFiles().first(); clear(); oemsh.set_input(csx_file.toStdString()); - parse_and_display(); + if(!parse_and_display()) + return; on_a_fit_triggered(); - QGuiApplication::restoreOverrideCursor(); } } @@ -329,8 +336,11 @@ void MainWindow::save_csx_file() { } QGuiApplication::setOverrideCursor(Qt::WaitCursor); - oemsh.write(); - ui->statusBar->showMessage("Saved file \"" + csx_file + "\""); + if(auto res = oemsh.write() + ; res) + ui->statusBar->showMessage("Saved file \"" + csx_file + "\""); + else + ui->statusBar->showMessage("Failed to save file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); QGuiApplication::restoreOverrideCursor(); } diff --git a/src/ui/qt/main_window.hpp b/src/ui/qt/main_window.hpp index 5dd1a17e..26ae48a2 100644 --- a/src/ui/qt/main_window.hpp +++ b/src/ui/qt/main_window.hpp @@ -88,7 +88,7 @@ private slots: MainWindow(app::OpenEMSH& oemsh, QWidget* parent = nullptr); ~MainWindow() override; - void parse_and_display(); + bool parse_and_display(); void clear(); protected: diff --git a/src/utils/expected_utils.hpp b/src/utils/expected_utils.hpp new file mode 100644 index 00000000..e7253cca --- /dev/null +++ b/src/utils/expected_utils.hpp @@ -0,0 +1,27 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include + +// Example: TRY(func()); +//****************************************************************************** +#define TRY(x) \ + { /* TRY */ \ + if(auto res = (x); !res.has_value()) \ + return std::unexpected(std::move(res.error())); \ + } + +// Example: UNWRAP(func(), [&](auto& val) { var = val; }); +//****************************************************************************** +#define UNWRAP(x, unwrap) \ + { /* UNWRAP */ \ + if(auto res = (x); res.has_value()) \ + unwrap(res.value()); \ + else \ + return std::unexpected(std::move(res.error())); \ + } From bf94dd52c6ff1c3100b222bd3e8dc9392c48fd10 Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Thu, 4 Dec 2025 04:39:17 +0100 Subject: [PATCH 3/7] fix : SerializerToCsx overwriting of RectilinearGrid in case of initial absence --- src/infra/serializers/serializer_to_csx.cpp | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/infra/serializers/serializer_to_csx.cpp b/src/infra/serializers/serializer_to_csx.cpp index 089c74a7..84851778 100644 --- a/src/infra/serializers/serializer_to_csx.cpp +++ b/src/infra/serializers/serializer_to_csx.cpp @@ -4,8 +4,6 @@ /// @author Thomas Lepoix ///***************************************************************************** -#include - #include #include "domain/mesh/meshline.hpp" @@ -58,6 +56,14 @@ SerializerToCsx::SerializerToCsx(filesystem::path const& input, filesystem::path , output(output) {} +//****************************************************************************** +pugi::xml_node find_or_add_child(pugi::xml_node& node, char const* child) { + if(pugi::xml_node c = node.child(child); c.empty()) + return node.append_child(child); + else + return c; +} + //****************************************************************************** void SerializerToCsx::visit(Board& board) { pugi::xml_document doc; @@ -68,7 +74,10 @@ void SerializerToCsx::visit(Board& board) { return; } - pugi::xpath_node const grid = doc.select_node("/openEMS/ContinuousStructure/RectilinearGrid"); + pugi::xml_node oems = find_or_add_child(doc, "openEMS"); + pugi::xml_node csx = find_or_add_child(oems, "ContinuousStructure"); + pugi::xml_node grid = find_or_add_child(csx, "RectilinearGrid"); + grid.remove_children(); auto const add_meshlines_to_xml_doc = [this, &grid, &board](Axis const axis) { string out; @@ -82,9 +91,7 @@ void SerializerToCsx::visit(Board& board) { if(!out.empty()) out.pop_back(); - cerr << "lines: " << out << endl << endl; - grid.node().select_node(to_xml_node(axis).c_str()) - .node().text().set(out.c_str()); + grid.append_child(to_xml_node(axis).c_str()).text().set(out.c_str()); }; if(params.with_axis_x) @@ -94,6 +101,5 @@ void SerializerToCsx::visit(Board& board) { if(params.with_axis_z) add_meshlines_to_xml_doc(Z); - cerr << "output: " << output << endl; - doc.save_file(output.native().c_str()); + doc.save_file(output.native().c_str(), " "); } From afd32e6428d6138bb0f77282939ec551e66bc1d3 Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Thu, 4 Dec 2025 04:41:25 +0100 Subject: [PATCH 4/7] refactor : add verbose log of successful write --- src/main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index ae133668..a476d5a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,6 +57,12 @@ int main(int argc, char* argv[]) { res.error()) << std::endl; return EXIT_FAILURE; + } else if(oemsh.get_params().verbose) { + std::cerr + << std::format( + "Saved file \"{}\"", + oemsh.get_params().output.generic_string()) + << std::endl; } } else { QApplication a(argc, argv); From 21e52511e8e5368b954b58629084dce1d6be1a39 Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Thu, 4 Dec 2025 23:03:53 +0100 Subject: [PATCH 5/7] add --save-oemsh-params : export GlobalParams in CSX file --- src/infra/serializers/serializer_to_csx.cpp | 29 ++++++++++++++++++--- src/infra/serializers/serializer_to_csx.hpp | 1 + src/ui/cli/cli.cpp | 1 + 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/infra/serializers/serializer_to_csx.cpp b/src/infra/serializers/serializer_to_csx.cpp index 84851778..456901df 100644 --- a/src/infra/serializers/serializer_to_csx.cpp +++ b/src/infra/serializers/serializer_to_csx.cpp @@ -57,7 +57,15 @@ SerializerToCsx::SerializerToCsx(filesystem::path const& input, filesystem::path {} //****************************************************************************** -pugi::xml_node find_or_add_child(pugi::xml_node& node, char const* child) { +pugi::xml_node find_or_prepend_child(pugi::xml_node& node, char const* child) { + if(pugi::xml_node c = node.child(child); c.empty()) + return node.prepend_child(child); + else + return c; +} + +//****************************************************************************** +pugi::xml_node find_or_append_child(pugi::xml_node& node, char const* child) { if(pugi::xml_node c = node.child(child); c.empty()) return node.append_child(child); else @@ -74,9 +82,9 @@ void SerializerToCsx::visit(Board& board) { return; } - pugi::xml_node oems = find_or_add_child(doc, "openEMS"); - pugi::xml_node csx = find_or_add_child(oems, "ContinuousStructure"); - pugi::xml_node grid = find_or_add_child(csx, "RectilinearGrid"); + pugi::xml_node oems = find_or_append_child(doc, "openEMS"); + pugi::xml_node csx = find_or_append_child(oems, "ContinuousStructure"); + pugi::xml_node grid = find_or_append_child(csx, "RectilinearGrid"); grid.remove_children(); auto const add_meshlines_to_xml_doc = [this, &grid, &board](Axis const axis) { @@ -101,5 +109,18 @@ void SerializerToCsx::visit(Board& board) { if(params.with_axis_z) add_meshlines_to_xml_doc(Z); + if(params.with_oemsh_params) { + auto const& p = board.global_params->get_current_state(); + pugi::xml_node oemsh = find_or_prepend_child(doc, "OpenEMSH"); + pugi::xml_node global_params = find_or_append_child(oemsh, "GlobalParams"); + global_params.remove_attributes(); + global_params.append_attribute("ProximityLimit").set_value(p.proximity_limit); + global_params.append_attribute("Smoothness").set_value(p.smoothness); + global_params.append_attribute("dmax").set_value(p.dmax); + global_params.append_attribute("lmin").set_value(p.lmin); + } else { + doc.remove_child("OpenEMSH"); + } + doc.save_file(output.native().c_str(), " "); } diff --git a/src/infra/serializers/serializer_to_csx.hpp b/src/infra/serializers/serializer_to_csx.hpp index 317343b5..f483d1c9 100644 --- a/src/infra/serializers/serializer_to_csx.hpp +++ b/src/infra/serializers/serializer_to_csx.hpp @@ -24,6 +24,7 @@ class SerializerToCsx final : public domain::EntityVisitor { bool with_axis_x = true; bool with_axis_y = true; bool with_axis_z = true; + bool with_oemsh_params = false; }; static std::expected run( diff --git a/src/ui/cli/cli.cpp b/src/ui/cli/cli.cpp index 1369be0c..e4ed764b 100644 --- a/src/ui/cli/cli.cpp +++ b/src/ui/cli/cli.cpp @@ -170,6 +170,7 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { app.add_flag("--meshlines", params.with_meshlines, "Include regular meshlines in output.")->group("Output options")->default_str(to_string(params.with_meshlines)); app.add_flag("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->default_str(to_string(params.with_meshline_policies)); // app.add_flag("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->capture_default_str(); + app.add_flag("--save-oemsh-params", params.with_oemsh_params, "Include OpenEMSH parameters used for this mesh.")->group("Output options")->default_str(to_string(params.with_oemsh_params)); app.preparse_callback([g](size_t argc) { if(argc == 0) From e384e33d53811c2a20aa9e4f85cf9aeb8eb837c5 Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Fri, 5 Dec 2025 17:04:44 +0100 Subject: [PATCH 6/7] refactor : fix some badsmells & some older Qt compatibility issue --- src/infra/parsers/parser_from_csx.cpp | 4 ++-- src/main.cpp | 4 ++-- src/ui/qt/main_window.cpp | 17 ++++++++++------- src/utils/expected_utils.hpp | 10 +++++----- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/infra/parsers/parser_from_csx.cpp b/src/infra/parsers/parser_from_csx.cpp index 83899f5b..9742836b 100644 --- a/src/infra/parsers/parser_from_csx.cpp +++ b/src/infra/parsers/parser_from_csx.cpp @@ -311,9 +311,9 @@ ParserFromCsx::~ParserFromCsx() = default; //****************************************************************************** expected ParserFromCsx::parse() { pugi::xml_document doc; - pugi::xml_parse_result res = doc.load_file(input.native().c_str()); - if(res.status != pugi::status_ok) { + if(auto res = doc.load_file(input.native().c_str()) + ; res.status != pugi::status_ok) { return unexpected(res.description()); } diff --git a/src/main.cpp b/src/main.cpp index a476d5a5..199515d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,7 @@ int main(int argc, char* argv[]) { }); if(!oemsh.get_params().gui) { - if(auto res = oemsh.parse(); !res) { + if(auto res = oemsh.parse(); !res.has_value()) { std::cerr << std::format( "Error parsing file \"{}\" : {}", @@ -49,7 +49,7 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } oemsh.run_all_steps(); - if(auto res = oemsh.write(); !res) { + if(auto res = oemsh.write(); !res.has_value()) { std::cerr << std::format( "Error saving file \"{}\" : {}", diff --git a/src/ui/qt/main_window.cpp b/src/ui/qt/main_window.cpp index ccdbdae8..9fd79a06 100644 --- a/src/ui/qt/main_window.cpp +++ b/src/ui/qt/main_window.cpp @@ -86,7 +86,7 @@ bool MainWindow::parse_and_display() { QGuiApplication::setOverrideCursor(Qt::WaitCursor); if(auto res = oemsh.parse() - ; !res) { + ; !res.has_value()) { QGuiApplication::restoreOverrideCursor(); ui->statusBar->showMessage("Error parsing file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); return false; @@ -327,8 +327,9 @@ void MainWindow::save_csx_file() { if(oemsh.is_about_overwriting()) { auto res = QMessageBox::warning(this, "", - QString("You are about overwriting the file \"%1\", do you want to continue?") - .arg(oemsh.get_params().output.generic_string()), + QString::fromStdString(std::format( + "You are about overwriting the file \"{}\", do you want to continue?", + oemsh.get_params().output.generic_string())), QMessageBox::Cancel | QMessageBox::Save); if(res != QMessageBox::Save) { return; @@ -337,7 +338,7 @@ void MainWindow::save_csx_file() { QGuiApplication::setOverrideCursor(Qt::WaitCursor); if(auto res = oemsh.write() - ; res) + ; res.has_value()) ui->statusBar->showMessage("Saved file \"" + csx_file + "\""); else ui->statusBar->showMessage("Failed to save file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); @@ -348,11 +349,13 @@ void MainWindow::save_csx_file() { void MainWindow::on_a_file_save_triggered() { if(oemsh.get_params().output.empty()) { oemsh.set_output(csx_file.toStdString()); - } else if(csx_file != oemsh.get_params().output.generic_string()) { + } else if(csx_file != QString::fromStdString(oemsh.get_params().output.generic_string())) { auto res = QMessageBox::question(this, "", - QString("You are about saving to the file \"%1\" which is different from the input file \"%2\", do you want to continue?") - .arg(oemsh.get_params().output.generic_string(), oemsh.get_params().input.generic_string()), + QString::fromStdString(std::format( + "You are about saving to the file \"{}\" which is different from the input file \"{}\", do you want to continue?", + oemsh.get_params().output.generic_string(), + oemsh.get_params().input.generic_string())), QMessageBox::Cancel | QMessageBox::Save); if(res == QMessageBox::Save) { csx_file = QString::fromStdString(oemsh.get_params().output.generic_string()); diff --git a/src/utils/expected_utils.hpp b/src/utils/expected_utils.hpp index e7253cca..b13f739d 100644 --- a/src/utils/expected_utils.hpp +++ b/src/utils/expected_utils.hpp @@ -12,16 +12,16 @@ //****************************************************************************** #define TRY(x) \ { /* TRY */ \ - if(auto res = (x); !res.has_value()) \ - return std::unexpected(std::move(res.error())); \ + if(auto MACRO_TRY_res = (x); !MACRO_TRY_res.has_value()) \ + return std::unexpected(std::move(MACRO_TRY_res.error())); \ } // Example: UNWRAP(func(), [&](auto& val) { var = val; }); //****************************************************************************** #define UNWRAP(x, unwrap) \ { /* UNWRAP */ \ - if(auto res = (x); res.has_value()) \ - unwrap(res.value()); \ + if(auto MACRO_UNWRAP_res = (x); MACRO_UNWRAP_res.has_value()) \ + unwrap(MACRO_UNWRAP_res.value()); \ else \ - return std::unexpected(std::move(res.error())); \ + return std::unexpected(std::move(MACRO_UNWRAP_res.error())); \ } From 1b66d78c758ad6a13985194761db10cea389676c Mon Sep 17 00:00:00 2001 From: "tlepoix@localhost" Date: Sat, 6 Dec 2025 03:41:35 +0100 Subject: [PATCH 7/7] fix some Cli flags --- src/ui/cli/cli.cpp | 10 +++---- src/utils/map_utils.hpp | 19 ++++++++++++++ test/unit/CMakeLists.txt | 1 + test/unit/utils/test_map_utils.cpp | 42 ++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/utils/map_utils.hpp create mode 100644 test/unit/utils/test_map_utils.cpp diff --git a/src/ui/cli/cli.cpp b/src/ui/cli/cli.cpp index e4ed764b..eff48099 100644 --- a/src/ui/cli/cli.cpp +++ b/src/ui/cli/cli.cpp @@ -16,6 +16,7 @@ #include #include "utils/concepts.hpp" +#include "utils/map_utils.hpp" #include "utils/unreachable.hpp" #include "cli.hpp" @@ -138,7 +139,7 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { { "prettyprint", app::OpenEMSH::Params::OutputFormat::PRETTYPRINT } }; // https://github.com/CLIUtils/CLI11/issues/554#issuecomment-932782337 - app.add_option("--output-format", params.output_format, "Output format.")->transform(CLI::CheckedTransformer(map, CLI::ignore_case).description(CLI::detail::generate_map(CLI::detail::smart_deref(map), true))); + app.add_option("--output-format", params.output_format, "Output format.")->transform(CLI::CheckedTransformer(map, CLI::ignore_case).description(CLI::detail::generate_map(CLI::detail::smart_deref(map), true)))->default_str(reverse_kv(map).at(params.output_format)); app.add_flag("--no-yz", [¶ms](size_t) { params.with_yz = false; }, "Don't process YZ plane.")->group("Input options"); app.add_flag("--no-zx", [¶ms](size_t) { params.with_zx = false; }, "Don't process ZX plane.")->group("Input options"); @@ -167,10 +168,9 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { app.add_flag("--no-x", [¶ms](size_t) { params.with_axis_x = false; }, "Don't include X axis meshlines in output.")->group("Output options"); app.add_flag("--no-y", [¶ms](size_t) { params.with_axis_y = false; }, "Don't include Y axis meshlines in output.")->group("Output options"); app.add_flag("--no-z", [¶ms](size_t) { params.with_axis_z = false; }, "Don't include Z axis meshlines in output.")->group("Output options"); - app.add_flag("--meshlines", params.with_meshlines, "Include regular meshlines in output.")->group("Output options")->default_str(to_string(params.with_meshlines)); - app.add_flag("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->default_str(to_string(params.with_meshline_policies)); -// app.add_flag("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->capture_default_str(); - app.add_flag("--save-oemsh-params", params.with_oemsh_params, "Include OpenEMSH parameters used for this mesh.")->group("Output options")->default_str(to_string(params.with_oemsh_params)); + app.add_flag("--save-oemsh-params", params.with_oemsh_params, "Include OpenEMSH parameters used for this mesh.")->group("Output options"); + app.add_option("--meshlines", params.with_meshlines, "Include regular meshlines in output.")->group("Output options")->default_str(to_string(params.with_meshlines)); + app.add_option("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->default_str(to_string(params.with_meshline_policies)); app.preparse_callback([g](size_t argc) { if(argc == 0) diff --git a/src/utils/map_utils.hpp b/src/utils/map_utils.hpp new file mode 100644 index 00000000..b6c96b15 --- /dev/null +++ b/src/utils/map_utils.hpp @@ -0,0 +1,19 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include +#include + +//****************************************************************************** +template> +std::map reverse_kv(std::map const& in) { + std::map out; + for(auto const& [k, v] : in) + out.emplace(v, k); + return out; +} diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 5b5db849..0f3f1f63 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -29,6 +29,7 @@ if( Catch2_FOUND ) "${CMAKE_CURRENT_SOURCE_DIR}/infra/serializers/test_serializer_to_plantuml.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/infra/utils/test_to_string.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_down_up_cast.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_map_utils.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_signum.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_vector_utils.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_tree_node.cpp" diff --git a/test/unit/utils/test_map_utils.cpp b/test/unit/utils/test_map_utils.cpp new file mode 100644 index 00000000..a00ddc83 --- /dev/null +++ b/test/unit/utils/test_map_utils.cpp @@ -0,0 +1,42 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#include + +#include + +#include "utils/map_utils.hpp" + +/// @test template> +/// std::map reverse_kv(std::map const& in) +///***************************************************************************** + +//****************************************************************************** +SCENARIO("template> \ +std::map reverse_kv(std::map const& in)", "[utils][map_utils]") { + enum class Value { + ZERO, + ONE, + TWO, + THREE + }; + GIVEN("A map of string keys and enum values") { + std::map m { + {"zero", Value::ZERO }, + {"one", Value::ONE }, + {"two", Value::TWO }, + {"three", Value::THREE } + }; + THEN("Should return a map of enum keys and string values") { + std::map r = reverse_kv(m); + REQUIRE(r.size() == m.size()); + REQUIRE(r.at(Value::ZERO) == "zero"); + REQUIRE(r.at(Value::ONE) == "one"); + REQUIRE(r.at(Value::TWO) == "two"); + REQUIRE(r.at(Value::THREE) == "three"); + } + } +}