diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96c31b47..6816a9b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources( openemsh "${CMAKE_CURRENT_SOURCE_DIR}/utils/entity.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/tree_node.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/state_management.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/utils/logger.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/progress.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/domain/geometrics/space.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/domain/geometrics/relation.cpp" @@ -84,6 +85,7 @@ add_executable( openemsh_bin WIN32 ) target_sources( openemsh_bin PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/ui/cli/cli.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ui/cli/logger.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/cli/progress.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/utils/nodegraph/highlightable.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/utils/nodegraph/port.cpp" @@ -132,6 +134,7 @@ target_sources( openemsh_bin "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/about_dialog.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/icons.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/main_window.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/logger.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/progress.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/style.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/qt/resources.qrc" diff --git a/src/domain/board.cpp b/src/domain/board.cpp index 8555e5b5..b0189133 100644 --- a/src/domain/board.cpp +++ b/src/domain/board.cpp @@ -5,6 +5,7 @@ ///***************************************************************************** #include +#include #include #include @@ -65,10 +66,16 @@ shared_ptr Board::Builder::build(Params&& params) { return make_shared( std::move(polygons), std::move(fixed_meshline_policy_creators), + std::move(material), std::move(params), Caretaker::singleton().get_history_root()); } +//****************************************************************************** +void Board::Builder::set_background_material(shared_ptr background) { + material = background; +} + // TODO should be in meshline manager? //****************************************************************************** void Board::Builder::add_fixed_meshline_policy(Axis const axis, Coord const coord) { @@ -146,12 +153,14 @@ Board::Board(PlaneSpace>>&& polygons, Params&& params Board::Board( PlaneSpace>>&& polygons, AxisSpace>>&& fixed_meshline_policy_creators, + shared_ptr&& background, Params&& params, Timepoint* t) : Originator(t, BoardState(std::move(polygons))) , global_params(make_shared(std::move(params), t)) , conflict_manager(make_shared(t)) , line_policy_manager(make_shared(global_params.get(), t)) +, material(background) , fixed_meshline_policy_creators(std::move(fixed_meshline_policy_creators)) { conflict_manager->init(line_policy_manager.get()); @@ -196,8 +205,7 @@ pair, remove_const_t> Board::f if(!materials.empty()) return materials.back(); else - // TODO if no material return Board::background_material - return {}; + return { material, std::numeric_limits>::min() }; } //****************************************************************************** @@ -661,9 +669,7 @@ void Board::detect_individual_edges() { //****************************************************************************** void Board::add_fixed_meshline_policies() { -#warning TODO -// for(auto const& axis : AllAxis) - for(auto const& axis : { X, Y }) + for(auto const& axis : AllAxis) add_fixed_meshline_policies(axis); } diff --git a/src/domain/board.hpp b/src/domain/board.hpp index 3d783455..f50f4c77 100644 --- a/src/domain/board.hpp +++ b/src/domain/board.hpp @@ -24,6 +24,7 @@ #include "utils/state_management.hpp" #include "conflict_manager.hpp" #include "global.hpp" +#include "material.hpp" #include "meshline_policy_manager.hpp" //#include "meshline_policy.hpp" //#include "range.hpp" @@ -59,6 +60,7 @@ class Board class Builder { public: + void set_background_material(std::shared_ptr background); void add_fixed_meshline_policy(Axis axis, Coord coord); void add_polygon(Plane plane, std::shared_ptr const& material, std::string const& name, std::size_t priority, Polygon::RangeZ const& z_placement, std::initializer_list points); void add_polygon(Plane plane, std::shared_ptr const& material, std::string const& name, std::size_t priority, Polygon::RangeZ const& z_placement, std::vector>&& points); @@ -67,16 +69,19 @@ class Board [[nodiscard]] std::shared_ptr build(Params&& params = Params()); private: + std::shared_ptr material; PlaneSpace>> polygons; AxisSpace>> fixed_meshline_policy_creators; }; + std::shared_ptr material; AxisSpace>> const fixed_meshline_policy_creators; // Meant to delay MeshlinePolicies creation at Step time instead of Parse time. Board(PlaneSpace>>&& polygons, Params&& params, Timepoint* t); Board( PlaneSpace>>&& polygons, AxisSpace>>&& fixed_meshline_policy_creators, + std::shared_ptr&& background, Params&& params, Timepoint* t); diff --git a/src/domain/conflicts/conflict_colinear_edges.cpp b/src/domain/conflicts/conflict_colinear_edges.cpp index c481b768..846742f4 100644 --- a/src/domain/conflicts/conflict_colinear_edges.cpp +++ b/src/domain/conflicts/conflict_colinear_edges.cpp @@ -33,6 +33,7 @@ void ConflictColinearEdges::append(Edge* edge, Timepoint* t) { void ConflictColinearEdges::auto_solve(MeshlinePolicyManager& line_policy_manager) { size_t normal_min = 0; size_t normal_max = 0; + bool must_be_oneline = false; auto const& s = get_current_state(); @@ -49,15 +50,21 @@ void ConflictColinearEdges::auto_solve(MeshlinePolicyManager& line_policy_manage case Normal::YMIN: case Normal::ZMIN: ++normal_min; - break; + continue; case Normal::XMAX: case Normal::YMAX: case Normal::ZMAX: ++normal_max; + continue; + case Normal::NONE: + must_be_oneline = true; break; default: - break; + ::unreachable(); } + + if(must_be_oneline) + break; } optional const coord = domain::coord(s.edges.front()->p0(), s.edges.front()->axis); @@ -78,7 +85,11 @@ void ConflictColinearEdges::auto_solve(MeshlinePolicyManager& line_policy_manage }; if(coord) { - if(normal_min && normal_max) { + if(must_be_oneline) { + add_policy( + MeshlinePolicy::Policy::ONELINE, + MeshlinePolicy::Normal::NONE); + } else if(normal_min && normal_max) { add_policy( MeshlinePolicy::Policy::HALFS, MeshlinePolicy::Normal::NONE); diff --git a/src/domain/conflicts/conflict_too_close_meshline_policies.cpp b/src/domain/conflicts/conflict_too_close_meshline_policies.cpp index 54a2d539..ac7c0d3d 100644 --- a/src/domain/conflicts/conflict_too_close_meshline_policies.cpp +++ b/src/domain/conflicts/conflict_too_close_meshline_policies.cpp @@ -36,52 +36,49 @@ void ConflictTooCloseMeshlinePolicies::auto_solve(MeshlinePolicyManager& line_po if(a->axis != b->axis) return; - auto [policy, normal] = [&]() -> tuple, optional> { + auto [policy, normal, coord] = [&]() -> tuple, optional, optional> { auto const& state_a = a->get_current_state(); auto const& state_b = b->get_current_state(); - if(state_a.policy == MeshlinePolicy::Policy::THIRDS && state_b.policy == MeshlinePolicy::Policy::THIRDS) { + if(state_a.policy == MeshlinePolicy::Policy::ONELINE && state_b.policy == MeshlinePolicy::Policy::ONELINE) { + return { MeshlinePolicy::Policy::ONELINE, MeshlinePolicy::Normal::NONE, mid(a->coord, b->coord) }; + } else if(state_a.policy == MeshlinePolicy::Policy::ONELINE && state_b.policy != MeshlinePolicy::Policy::ONELINE) { + return { MeshlinePolicy::Policy::ONELINE, MeshlinePolicy::Normal::NONE, a->coord }; + } else if(state_a.policy != MeshlinePolicy::Policy::ONELINE && state_b.policy == MeshlinePolicy::Policy::ONELINE) { + return { MeshlinePolicy::Policy::ONELINE, MeshlinePolicy::Normal::NONE, b->coord }; + } else if(state_a.policy == MeshlinePolicy::Policy::THIRDS && state_b.policy == MeshlinePolicy::Policy::THIRDS) { if(state_a.normal != state_b.normal) { - return { MeshlinePolicy::Policy::HALFS, MeshlinePolicy::Normal::NONE }; + return { MeshlinePolicy::Policy::HALFS, MeshlinePolicy::Normal::NONE, mid(a->coord, b->coord) }; } else if(state_a.normal == MeshlinePolicy::Normal::MIN && state_b.normal == MeshlinePolicy::Normal::MIN) { - return { MeshlinePolicy::Policy::THIRDS, MeshlinePolicy::Normal::MIN }; + return { MeshlinePolicy::Policy::THIRDS, MeshlinePolicy::Normal::MIN, mid(a->coord, b->coord) }; } else if(state_a.normal == MeshlinePolicy::Normal::MAX && state_b.normal == MeshlinePolicy::Normal::MAX) { - return { MeshlinePolicy::Policy::THIRDS, MeshlinePolicy::Normal::MAX }; + return { MeshlinePolicy::Policy::THIRDS, MeshlinePolicy::Normal::MAX, mid(a->coord, b->coord) }; } } else if((state_a.policy == MeshlinePolicy::Policy::HALFS && state_b.policy == MeshlinePolicy::Policy::HALFS) || (state_a.policy == MeshlinePolicy::Policy::HALFS && state_b.policy == MeshlinePolicy::Policy::THIRDS) || (state_a.policy == MeshlinePolicy::Policy::THIRDS && state_b.policy == MeshlinePolicy::Policy::HALFS)) { - return { MeshlinePolicy::Policy::HALFS, MeshlinePolicy::Normal::NONE }; - } //else if(ONE and *) { // TODO } - // TODO should not have been created ? - // TODO only one, remove other? - // TODO one and one : merge + return { MeshlinePolicy::Policy::HALFS, MeshlinePolicy::Normal::NONE, mid(a->coord, b->coord) }; + } - return { nullopt, nullopt }; + return { nullopt, nullopt, nullopt }; } (); - if(policy && normal) { + if(policy && normal && coord) { auto [t, state] = make_next_state(); state.meshline_policy = line_policy_manager.add_meshline_policy( { this }, axis, policy.value(), normal.value(), - mid(a->coord, b->coord), + coord.value(), true, t); state.solution = state.meshline_policy; state.is_solved = true; set_state(t, state); } - - // TODO detect axis - // TODO detect 2 vs 3 rule (vs 1 rule?) - // TODO detect normal - // TODO create MLP - } // TODO Interval class : just mesh() from adjacent / neibourgh diff --git a/src/domain/geometrics/polygon.cpp b/src/domain/geometrics/polygon.cpp index a00c87a3..93d29abf 100644 --- a/src/domain/geometrics/polygon.cpp +++ b/src/domain/geometrics/polygon.cpp @@ -4,6 +4,9 @@ /// @author Thomas Lepoix ///***************************************************************************** +#include +#include + #include "utils/unreachable.hpp" #include "edge.hpp" #include "point.hpp" @@ -50,8 +53,8 @@ vector> detect_edges(vector> const& poi edges.push_back(make_shared(plane, prev, point.get(), t)); prev = point.get(); } - edges.shrink_to_fit(); + edges.shrink_to_fit(); return edges; } @@ -204,10 +207,26 @@ relation::PolygonPoint Polygon::relation_to(Point const& point) const noexcept { /// Check if two Z ranges overlap or just touch each other. ///***************************************************************************** bool does_overlap(Polygon::RangeZ const& a, Polygon::RangeZ const& b) noexcept { - return (b.min >= a.min && b.min <= a.max) - || (b.max >= a.min && b.max <= a.max) - || (a.min >= b.min && a.min <= b.max) - || (a.max >= b.min && a.max <= b.max); + return min(a.max, b.max) >= max(a.min, b.min); +} + +/// Points on axes guaranteed. CCW oriented. +///***************************************************************************** +vector> circle_to_points(Point const& center, double radius, size_t n_slices_per_quarter) { + if(!n_slices_per_quarter) + n_slices_per_quarter = 1; + + double const angle = (numbers::pi/2) / n_slices_per_quarter; + size_t const n = n_slices_per_quarter * 4; + + vector> points; + for(size_t i = 0; i < n; ++i) { + double a = angle * i; + points.emplace_back(make_unique(center.x + radius * cos(a), center.y + radius * sin(a))); + } + + points.shrink_to_fit(); + return points; } } // namespace domain diff --git a/src/domain/geometrics/polygon.hpp b/src/domain/geometrics/polygon.hpp index f1c6296a..ab6e8a7d 100644 --- a/src/domain/geometrics/polygon.hpp +++ b/src/domain/geometrics/polygon.hpp @@ -90,4 +90,7 @@ std::vector> detect_edges(std::vector> circle_to_points(Point const& center, double radius, std::size_t n_slices_per_quarter = 9); + } // namespace domain diff --git a/src/domain/global.hpp b/src/domain/global.hpp index b716906e..43df9b48 100644 --- a/src/domain/global.hpp +++ b/src/domain/global.hpp @@ -7,7 +7,10 @@ #pragma once #include +#include +#include +#include "geometrics/space.hpp" #include "utils/state_management.hpp" namespace domain { @@ -17,12 +20,14 @@ 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; + std::size_t lmin = 2; double dmax = 2.5; std::size_t diagonal_lmin = 2; double diagonal_dmax = 0.2; double consecutive_diagonal_minimal_angle = 20; // Limite between acute / obtuse angles. + + std::vector> input_fixed_meshlines; }; //****************************************************************************** diff --git a/src/domain/material.cpp b/src/domain/material.cpp index 4f0f887b..2aa5011b 100644 --- a/src/domain/material.cpp +++ b/src/domain/material.cpp @@ -38,8 +38,17 @@ Material::Material(Type type, string const& name, optional const& fill_co //****************************************************************************** strong_ordering Material::operator<=>(Material const& other) const noexcept { switch(type) { + case Type::PORT: + switch(other.type) { + case Type::PORT: return strong_ordering::equivalent; + case Type::CONDUCTOR: return strong_ordering::greater; + case Type::DIELECTRIC: return strong_ordering::greater; + case Type::AIR: return strong_ordering::greater; + default: ::unreachable(); + } case Type::CONDUCTOR: switch(other.type) { + case Type::PORT: return strong_ordering::less; case Type::CONDUCTOR: return strong_ordering::equivalent; case Type::DIELECTRIC: return strong_ordering::greater; case Type::AIR: return strong_ordering::greater; @@ -47,6 +56,7 @@ strong_ordering Material::operator<=>(Material const& other) const noexcept { } case Type::DIELECTRIC: switch(other.type) { + case Type::PORT: return strong_ordering::less; case Type::CONDUCTOR: return strong_ordering::less; case Type::DIELECTRIC: return strong_ordering::equivalent; case Type::AIR: return strong_ordering::greater; @@ -54,6 +64,7 @@ strong_ordering Material::operator<=>(Material const& other) const noexcept { } case Type::AIR: switch(other.type) { + case Type::PORT: return strong_ordering::less; case Type::CONDUCTOR: return strong_ordering::less; case Type::DIELECTRIC: return strong_ordering::less; case Type::AIR: return strong_ordering::equivalent; diff --git a/src/domain/material.hpp b/src/domain/material.hpp index a7eeb63f..9d7f9973 100644 --- a/src/domain/material.hpp +++ b/src/domain/material.hpp @@ -18,6 +18,7 @@ class Material { enum class Type { // DUMP, + PORT, // PEC, CONDUCTOR, DIELECTRIC, diff --git a/src/domain/mesh/meshline_policy.cpp b/src/domain/mesh/meshline_policy.cpp index eb6d4da4..6ebb37e1 100644 --- a/src/domain/mesh/meshline_policy.cpp +++ b/src/domain/mesh/meshline_policy.cpp @@ -23,14 +23,12 @@ MeshlinePolicy::MeshlinePolicy( Coord const coord, Timepoint* t, vector const& origins, - bool const is_enabled, - double const res_factor) + bool const is_enabled) : Originator(t, { .policy = policy, .normal = normal, .is_enabled = is_enabled, - .res_factor = res_factor, - .d = global_params->get_current_state().dmax / res_factor, + .d = global_params->get_current_state().dmax / 2, // TODO this seems to help while center of intervals is buggy .origins = origins }) , axis(axis) @@ -40,7 +38,8 @@ MeshlinePolicy::MeshlinePolicy( //****************************************************************************** optional MeshlinePolicy::mesh() { - if(get_current_state().policy == Policy::ONELINE) + auto const& state = get_current_state(); + if(state.is_enabled && state.policy == Policy::ONELINE) return Meshline(coord, this); else return nullopt; diff --git a/src/domain/mesh/meshline_policy.hpp b/src/domain/mesh/meshline_policy.hpp index d118b9e3..12e70126 100644 --- a/src/domain/mesh/meshline_policy.hpp +++ b/src/domain/mesh/meshline_policy.hpp @@ -71,8 +71,7 @@ class MeshlinePolicy Coord const coord, Timepoint* t, std::vector const& origins = {}, - bool const is_enabled = true, - double const res_factor = 2); + bool const is_enabled = true); std::optional mesh(); }; @@ -84,7 +83,6 @@ struct MeshlinePolicyState final MeshlinePolicy::Policy policy; MeshlinePolicy::Normal normal; bool is_enabled; - double res_factor; // TODO useful? d directly? come from params double d; ///< Distance betwen two lines (HALFS and THIRDS only). std::vector origins; std::vector meshlines; diff --git a/src/domain/meshline_policy_manager.cpp b/src/domain/meshline_policy_manager.cpp index 338ebc4f..d97b8b92 100644 --- a/src/domain/meshline_policy_manager.cpp +++ b/src/domain/meshline_policy_manager.cpp @@ -77,8 +77,7 @@ optional> detect_closest_meshline_policies( erase_if(dimension, [](MeshlinePolicy const* a) { - return (!a->get_current_state().is_enabled) - || a->get_current_state().policy == MeshlinePolicy::Policy::ONELINE; + return (!a->get_current_state().is_enabled); }); ranges::sort(dimension, @@ -90,7 +89,7 @@ optional> detect_closest_meshline_policies( array closest; for(size_t i = 1; i < dimension.size(); ++i) { - Coord space(abs((double) (dimension[i]->coord - dimension[i-1]->coord))); + Coord space(distance(dimension[i-1]->coord, dimension[i]->coord)); if(space < smallest_space) { smallest_space = space; closest = { dimension[i-1], dimension[i] }; diff --git a/src/infra/parsers/parser_from_csx.cpp b/src/infra/parsers/parser_from_csx.cpp index 9742836b..48ddb95a 100644 --- a/src/infra/parsers/parser_from_csx.cpp +++ b/src/infra/parsers/parser_from_csx.cpp @@ -4,9 +4,15 @@ /// @author Thomas Lepoix ///***************************************************************************** +#include +#include +#include #include #include +#include +#include #include +#include #include @@ -19,6 +25,7 @@ #include "parser_from_csx.hpp" #include "utils/expected_utils.hpp" +#include "utils/logger.hpp" #include "utils/progress.hpp" #include "utils/unreachable.hpp" #include "utils/vector_utils.hpp" @@ -46,6 +53,23 @@ constexpr optional to_plane(size_t const normdir) { } } +//****************************************************************************** +expected str_to_double(string_view const& str) { + double res; + auto [_, e] = std::from_chars(str.data(), str.data() + str.size(), res); + if(e == std::errc {}) + return res; + else + return unexpected(make_error_code(e).message()); +} + +//****************************************************************************** +size_t parse_priority(pugi::xml_node const& node, shared_ptr const& material) { + return (material && material->type == Material::Type::PORT) + ? numeric_limits::max() + : node.attribute("Priority").as_uint(); +} + //****************************************************************************** class ParserFromCsx::Pimpl { public: @@ -54,17 +78,31 @@ class ParserFromCsx::Pimpl { Board::Builder board; domain::Params domain_params; + set warning_unsupported_properties_types; + set warning_unsupported_properties_names; + set warning_unsupported_primitives_types; + set warning_unsupported_primitives_names; + Pimpl(ParserFromCsx::Params const& params); + expected parse_oemsh(pugi::xml_node const& node); expected 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); void parse_primitive_box(pugi::xml_node const& node, shared_ptr const& material, std::string name); + void parse_primitive_multibox(pugi::xml_node const& node, shared_ptr const& material, std::string name); void parse_primitive_linpoly(pugi::xml_node const& node, shared_ptr const& material, std::string name); + void parse_primitive_polygon(pugi::xml_node const& node, shared_ptr const& material, std::string name); + void parse_primitive_shpere(pugi::xml_node const& node, shared_ptr const& material, std::string name); + void parse_primitive_cylinder(pugi::xml_node const& node, shared_ptr const& material, std::string name); + void parse_primitive_point(pugi::xml_node const& node, shared_ptr const& material, std::string name); + void parse_primitive_curve(pugi::xml_node const& node, shared_ptr const& material, std::string name); private: + void warn_unsupported_property(string const& property_type, string const& property_name); + void warn_unsupported_primitive(string const& primitive_type, string const& primitive_name); }; //****************************************************************************** @@ -72,18 +110,71 @@ ParserFromCsx::Pimpl::Pimpl(ParserFromCsx::Params const& params) : params(params) {} +//****************************************************************************** +void ParserFromCsx::Pimpl::warn_unsupported_property(string const& primitive_type, string const& primitive_name) { + warning_unsupported_properties_types.emplace(primitive_type); + warning_unsupported_properties_names.emplace(primitive_name); +} + +//****************************************************************************** +void ParserFromCsx::Pimpl::warn_unsupported_primitive(string const& primitive_type, string const& primitive_name) { + warning_unsupported_primitives_types.emplace(primitive_type); + warning_unsupported_primitives_names.emplace(primitive_name); +} + +//****************************************************************************** +expected ParserFromCsx::Pimpl::parse_oemsh(pugi::xml_node const& node) { + pugi::xml_node global_params = node.child("GlobalParams"); + if(auto a = global_params.attribute("ProximityLimit"); a) domain_params.proximity_limit = a.as_double(); + if(auto a = global_params.attribute("Smoothness"); a) domain_params.smoothness = a.as_double(); + if(auto a = global_params.attribute("dmax"); a) domain_params.dmax = a.as_double(); + if(auto a = global_params.attribute("lmin"); a) domain_params.lmin = a.as_uint(); + + pugi::xml_node fixed_meshlines = node.child("FixedMeshlines"); + size_t delta_unit = fixed_meshlines.attribute("DeltaUnit").as_uint(1); + AxisSpace lines = { + fixed_meshlines.child_value("XLines"), + fixed_meshlines.child_value("YLines"), + fixed_meshlines.child_value("ZLines") + }; + for(Axis axis : AllAxis) { + for(auto const part : views::split(lines[axis], ',')) { + string_view str(part); + if(auto line = str_to_double(str); line.has_value()) + board.add_fixed_meshline_policy(axis, delta_unit * line.value()); + else + return unexpected(format("Invalid meshline value \"{}\": {}", str, line.error())); + } + } + return {}; +} + //****************************************************************************** expected ParserFromCsx::Pimpl::parse_grid(pugi::xml_node const& node) { std::size_t coord_system = node.attribute("CoordSystem").as_uint(); + std::size_t delta_unit = node.attribute("DeltaUnit").as_uint(1); 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 + AxisSpace lines = { + grid.child_value("XLines"), + grid.child_value("YLines"), + grid.child_value("ZLines") + }; + domain_params.has_grid_already = ranges::any_of(lines, [](auto const& str){ return !str.empty(); }); + // Second step : into fixed MLP + if(params.keep_old_mesh) { + for(Axis axis : AllAxis) { + for(auto const part : views::split(lines[axis], ',')) { + string_view str(part); + if(auto line = str_to_double(str); line.has_value()) + board.add_fixed_meshline_policy(axis, delta_unit * line.value()); + else + return unexpected(format("Invalid meshline value \"{}\": {}", str, line.error())); + } + } + } // TODO Third step : into vizualisable set of meshlines for comparison // } else if(coord_system == 1) { } else { @@ -108,35 +199,48 @@ shared_ptr ParserFromCsx::Pimpl::parse_property(pugi::xml_node const& return nullopt; }; - auto fill = parse_color(node.child("FillColor")); - auto edge = parse_color(node.child("EdgeColor")); - - if(node.name() == "Material"s) { - // https://github.com/thliebig/openEMS-Project/discussions/347 - // Currently do not take care of Isotropy=false - // as_double() selects the first term and ditch the part after - bool isotropy = node.attribute("Isotropy").as_bool(); - pugi::xml_node property = node.child("Property"); - double epsilon = property.attribute("Epsilon").as_double(Material::default_epsilon); - double mue = property.attribute("Mue").as_double(Material::default_mue); - double kappa = property.attribute("Kappa").as_double(Material::default_kappa); - double sigma = property.attribute("Sigma").as_double(Material::default_sigma); - return make_shared(Material::deduce_type(epsilon, mue, kappa), name, fill, edge); - } else if(node.name() == "Metal"s) { - return make_shared(Material::Type::CONDUCTOR, name, fill, edge); - } else if(node.name() == "ConductingSheet"s) { - double conductivity = node.attribute("Conductivity").as_double(); - double thickness = node.attribute("Thickness").as_double(); - return make_shared(Material::Type::CONDUCTOR, name, fill, edge); - } else if(node.name() == "LumpedElement"s) { - } else if(node.name() == "Excitation"s) { - } else if(node.name() == "ProbeBox"s) { - } else if(node.name() == "DumpBox"s) { - } else if(node.name() == "DebyeMaterial"s) { - } else if(node.name() == "LorentzMaterial"s) { - } else if(node.name() == "ResBox"s) { - } else if(node.name() == "Unknown"s) { - } else if(node.name() == "DiscMaterial"s) { + auto const parse_material_property = [](pugi::xml_node const& node) -> array { + return { + node.attribute("Epsilon").as_double(Material::default_epsilon), + node.attribute("Mue").as_double(Material::default_mue), + node.attribute("Kappa").as_double(Material::default_kappa), + node.attribute("Sigma").as_double(Material::default_sigma) + }; + }; + + if(node.name() == "BackgroundMaterial"s) { + auto [epsilon, mue, kappa, sigma] = parse_material_property(node); + return make_shared(Material::deduce_type(epsilon, mue, kappa), "BackgroundMaterial"); + } else { + auto fill = parse_color(node.child("FillColor")); + auto edge = parse_color(node.child("EdgeColor")); + + if(node.name() == "Material"s + || node.name() == "DispersiveMaterial"s + || node.name() == "DebyeMaterial"s + || node.name() == "LorentzMaterial"s) { + // https://github.com/thliebig/openEMS-Project/discussions/347 + // Currently do not take care of Isotropy=false + // as_double() selects the first term and ditch the part after + bool isotropy = node.attribute("Isotropy").as_bool(); + auto [epsilon, mue, kappa, sigma] = parse_material_property(node.child("Property")); + return make_shared(Material::deduce_type(epsilon, mue, kappa), name, fill, edge); + } else if(node.name() == "Metal"s) { + return make_shared(Material::Type::CONDUCTOR, name, fill, edge); + } else if(node.name() == "ConductingSheet"s) { + double conductivity = node.attribute("Conductivity").as_double(); + double thickness = node.attribute("Thickness").as_double(); + return make_shared(Material::Type::CONDUCTOR, name, fill, edge); + } else if(node.name() == "LumpedElement"s + || node.name() == "Excitation"s + || node.name() == "ProbeBox"s) { + return make_shared(Material::Type::PORT, name, fill, edge); + } else if(node.name() == "DumpBox"s + || node.name() == "ResBox"s + || node.name() == "DiscMaterial"s + || node.name() == "Unknown"s) { + warn_unsupported_property(node.name(), name); + } } return shared_ptr(); @@ -148,29 +252,51 @@ bool ParserFromCsx::Pimpl::parse_primitive(pugi::xml_node const& node, shared_pt string property_name(node.parent().parent().attribute("Name").as_string()); string name(property_name + "::" + to_string(primitives_ids.at(node))); + if(!node.child("Transformation").first_child().empty()) { + warn_unsupported_primitive(format("Transformed {}", node.name()), name); + return false; + } + using pugi::char_t; if(node.name() == "Box"s) { parse_primitive_box(node, material, name); return true; + } else if(node.name() == "MultiBox"s) { + parse_primitive_multibox(node, material, name); + return true; } else if(node.name() == "LinPoly"s) { parse_primitive_linpoly(node, material, name); return true; - } else if(node.name() == "Polyhedron"s) { } else if(node.name() == "Polygon"s) { - } else if(node.name() == "RotPoly"s) { + parse_primitive_polygon(node, material, name); + return true; } else if(node.name() == "Sphere"s) { + parse_primitive_shpere(node, material, name); + return true; } else if(node.name() == "Cylinder"s) { - } else if(node.name() == "Wire"s) { - } else if(node.name() == "CylindricalShell"s) { - } else if(node.name() == "User-Defined"s) { + parse_primitive_cylinder(node, material, name); + return true; + } else if(node.name() == "Point"s) { + parse_primitive_point(node, material, name); + return true; } else if(node.name() == "Curve"s) { + parse_primitive_curve(node, material, name); + return true; + } else if(node.name() == "Polyhedron"s + || node.name() == "PolyhedronReader"s + || node.name() == "RotPoly"s + || node.name() == "SphericalShell"s + || node.name() == "CylindricalShell"s + || node.name() == "Wire"s + || node.name() == "User-Defined"s) { + warn_unsupported_primitive(node.name(), name); } return false; } //****************************************************************************** void ParserFromCsx::Pimpl::parse_primitive_box(pugi::xml_node const& node, shared_ptr const& material, string name) { - size_t priority = node.attribute("Priority").as_uint(); + size_t priority = parse_priority(node, material); pugi::xml_node node_p1 = node.child("P1"); pugi::xml_node node_p2 = node.child("P2"); Point3D p1( @@ -190,9 +316,30 @@ void ParserFromCsx::Pimpl::parse_primitive_box(pugi::xml_node const& node, share // TODO if property == ConductingSheet : add_fixed_meshline_policy() } +//****************************************************************************** +void ParserFromCsx::Pimpl::parse_primitive_multibox(pugi::xml_node const& node, shared_ptr const& material, string name) { + size_t priority = parse_priority(node, material); + for(auto const& [s, e] : views::zip(node.children("StartP"), node.children("EndP")) | views::as_const) { + Point3D p1( + s.attribute("X").as_double(), + s.attribute("Y").as_double(), + s.attribute("Z").as_double()); + Point3D p2( + e.attribute("X").as_double(), + e.attribute("Y").as_double(), + e.attribute("Z").as_double()); + if(params.with_yz) + board.add_polygon_from_box(YZ, material, name, priority, { p1.x, p2.x }, { p1.y, p1.z }, { p2.y, p2.z }); + if(params.with_zx) + board.add_polygon_from_box(ZX, material, name, priority, { p1.y, p2.y }, { p1.z, p1.x }, { p2.z, p2.x }); + if(params.with_xy) + board.add_polygon_from_box(XY, material, name, priority, { p1.z, p2.z }, { p1.x, p1.y }, { p2.x, p2.y }); + } +} + //****************************************************************************** void ParserFromCsx::Pimpl::parse_primitive_linpoly(pugi::xml_node const& node, shared_ptr const& material, string name) { - size_t priority = node.attribute("Priority").as_uint(); + size_t priority = parse_priority(node, material); double elevation = node.attribute("Elevation").as_double(); // offset in normdir double length = node.attribute("Length").as_double(); // height in normdir size_t normdir = node.attribute("NormDir").as_uint(); // (0->x, 1->y, 2->z) @@ -272,6 +419,207 @@ void ParserFromCsx::Pimpl::parse_primitive_linpoly(pugi::xml_node const& node, s } } +//****************************************************************************** +void ParserFromCsx::Pimpl::parse_primitive_polygon(pugi::xml_node const& node, shared_ptr const& material, string name) { + size_t priority = parse_priority(node, material); + double elevation = node.attribute("Elevation").as_double(); // offset in normdir + size_t normdir = node.attribute("NormDir").as_uint(); // (0->x, 1->y, 2->z) + optional plane = to_plane(normdir); + optional normal = to_axis(normdir); + if(!plane || !normal) + return; + + vector> points; + for(auto const& vertex : node.children("Vertex")) + points.push_back(make_unique( + vertex.attribute("X1").as_double(), + vertex.attribute("X2").as_double())); + + Bounding2D bounding(detect_bounding(points)); + + board.add_polygon(plane.value(), material, name, priority, { elevation, elevation }, std::move(points)); + board.add_fixed_meshline_policy(normal.value(), elevation); +} + +//****************************************************************************** +void ParserFromCsx::Pimpl::parse_primitive_shpere(pugi::xml_node const& node, shared_ptr const& material, std::string name) { + size_t priority = parse_priority(node, material); + double radius = node.attribute("Radius").as_double(); + pugi::xml_node node_center = node.child("Center"); + Point3D center( + node_center.attribute("X").as_double(), + node_center.attribute("Y").as_double(), + node_center.attribute("Z").as_double()); + + // TODO Z placement here is the bounding box + if(params.with_yz) + board.add_polygon(YZ, material, name, priority, { center.x - radius, center.x + radius }, circle_to_points({ center.y, center.z }, radius)); + if(params.with_zx) + board.add_polygon(ZX, material, name, priority, { center.y - radius, center.y + radius }, circle_to_points({ center.z, center.x }, radius)); + if(params.with_xy) + board.add_polygon(XY, material, name, priority, { center.z - radius, center.z + radius }, circle_to_points({ center.x, center.y }, radius)); +} + +//****************************************************************************** +void ParserFromCsx::Pimpl::parse_primitive_cylinder(pugi::xml_node const& node, shared_ptr const& material, std::string name) { + size_t priority = parse_priority(node, material); + double radius = node.attribute("Radius").as_double(); + pugi::xml_node node_p1 = node.child("P1"); + pugi::xml_node node_p2 = node.child("P2"); + Point3D p1( + node_p1.attribute("X").as_double(), + node_p1.attribute("Y").as_double(), + node_p1.attribute("Z").as_double()); + Point3D p2( + node_p2.attribute("X").as_double(), + node_p2.attribute("Y").as_double(), + node_p2.attribute("Z").as_double()); + + // If orthogonal circle + polygons. + if(p1.y == p2.y && p1.z == p2.z) { + if(params.with_yz) + board.add_polygon(YZ, material, name, priority, { p1.x, p2.x }, circle_to_points({ p1.y, p1.z }, radius)); + if(params.with_zx) + // TODO Z placement here is the bounding box + board.add_polygon_from_box(ZX, material, name, priority, + { p1.y, p2.y }, + { p1.z - radius/2, p1.x }, + { p2.z + radius/2, p2.x }); + if(params.with_xy) + // TODO Z placement here is the bounding box + board.add_polygon_from_box(XY, material, name, priority, + { p1.z, p2.z }, + { p1.x, p1.y - radius/2 }, + { p2.x, p2.y + radius/2 }); + } else if(p1.z == p2.z && p1.x == p2.x) { + if(params.with_zx) + board.add_polygon(ZX, material, name, priority, { p1.y, p2.y }, circle_to_points({ p1.z, p1.x }, radius)); + if(params.with_xy) + // TODO Z placement here is the bounding box + board.add_polygon_from_box(XY, material, name, priority, + { p1.z, p2.z }, + { p1.x - radius/2, p1.y }, + { p2.x + radius/2, p2.y }); + if(params.with_yz) + // TODO Z placement here is the bounding box + board.add_polygon_from_box(YZ, material, name, priority, + { p1.x, p2.x }, + { p1.y, p1.z - radius/2 }, + { p2.y, p2.z + radius/2 }); + } else if(p1.x == p2.x && p1.y == p2.y) { + if(params.with_xy) + board.add_polygon(XY, material, name, priority, { p1.z, p2.z }, circle_to_points({ p1.x, p1.y }, radius)); + if(params.with_yz) + // TODO Z placement here is the bounding box + board.add_polygon_from_box(YZ, material, name, priority, + { p1.x, p2.x }, + { p1.y - radius/2, p1.z }, + { p2.y + radius/2, p2.z }); + if(params.with_zx) + // TODO Z placement here is the bounding box + board.add_polygon_from_box(ZX, material, name, priority, + { p1.y, p2.y }, + { p1.z, p1.x - radius/2 }, + { p2.z, p2.x + radius/2 }); + } else { + warn_unsupported_primitive(format("{} (with diagonal axis)", node.name()), name); + } +} + +//****************************************************************************** +void ParserFromCsx::Pimpl::parse_primitive_point(pugi::xml_node const& node, shared_ptr const& material, std::string name) { +// size_t priority = numeric_limits::max(); + Point3D p( + node.attribute("X").as_double(), + node.attribute("Y").as_double(), + node.attribute("Z").as_double()); + + // TODO board.add_point() + if(params.with_yz) { +// board.add_point(YZ, material, name, /*priority,*/ { p.y, p.z }); + board.add_fixed_meshline_policy(Y, p.y); + board.add_fixed_meshline_policy(Z, p.z); + } + if(params.with_zx) { +// board.add_point(ZX, material, name, /*priority,*/ { p.z, p.x }); + board.add_fixed_meshline_policy(Z, p.z); + board.add_fixed_meshline_policy(X, p.x); + } + if(params.with_xy) { +// board.add_point(XY, material, name, /*priority,*/ { p.x, p.y }); + board.add_fixed_meshline_policy(X, p.x); + board.add_fixed_meshline_policy(Y, p.y); + } +} + +//****************************************************************************** +void ParserFromCsx::Pimpl::parse_primitive_curve(pugi::xml_node const& node, shared_ptr const& material, std::string name) { + size_t priority = numeric_limits::max(); + vector vertices; + for(auto const& vertex : node.children("Vertex")) { + vertices.emplace_back( + vertex.attribute("X").as_double(), + vertex.attribute("Y").as_double(), + vertex.attribute("Z").as_double()); + } + + // TODO support onepoint polygon and open polygons (not to double points, edges & angles) + if(params.with_yz) { + // TODO Z placement here is the bounding box + Polygon::RangeZ z_placement { + ranges::min(vertices, [&](Point3D const& a, Point3D const& b) { return a.x < b.x; }).x, + ranges::max(vertices, [&](Point3D const& a, Point3D const& b) { return a.x > b.x; }).x + }; + vector> points; + for(auto const& vertex : vertices) + points.push_back(make_unique( + vertex.y, + vertex.z)); + for(auto const& vertex : vertices | views::drop(1) | views::reverse | views::drop(1)) + points.push_back(make_unique( + vertex.y, + vertex.z)); + points.shrink_to_fit(); + board.add_polygon(YZ, material, name, priority, z_placement, std::move(points)); + } + if(params.with_zx) { + // TODO Z placement here is the bounding box + Polygon::RangeZ z_placement { + ranges::min(vertices, [&](Point3D const& a, Point3D const& b) { return a.y < b.y; }).y, + ranges::max(vertices, [&](Point3D const& a, Point3D const& b) { return a.y > b.y; }).y + }; + vector> points; + for(auto const& vertex : vertices) + points.push_back(make_unique( + vertex.z, + vertex.x)); + for(auto const& vertex : vertices | views::drop(1) | views::reverse | views::drop(1)) + points.push_back(make_unique( + vertex.z, + vertex.x)); + points.shrink_to_fit(); + board.add_polygon(ZX, material, name, priority, z_placement, std::move(points)); + } + if(params.with_xy) { + // TODO Z placement here is the bounding box + Polygon::RangeZ z_placement { + ranges::min(vertices, [&](Point3D const& a, Point3D const& b) { return a.z < b.z; }).z, + ranges::max(vertices, [&](Point3D const& a, Point3D const& b) { return a.z > b.z; }).z + }; + vector> points; + for(auto const& vertex : vertices) + points.push_back(make_unique( + vertex.x, + vertex.y)); + for(auto const& vertex : vertices | views::drop(1) | views::reverse | views::drop(1)) + points.push_back(make_unique( + vertex.x, + vertex.y)); + points.shrink_to_fit(); + board.add_polygon(XY, material, name, priority, z_placement, std::move(points)); + } +} + //****************************************************************************** expected, string> ParserFromCsx::run(std::filesystem::path const& input) { return ParserFromCsx::run(input, {}); @@ -289,6 +637,9 @@ expected, string> ParserFromCsx::run(std::filesystem::path con ParserFromCsx parser(input, std::move(params)); TRY(parser.parse()); override_domain_params(parser.domain_params); + for(auto const& [axis, coord] : parser.domain_params.input_fixed_meshlines) + parser.pimpl->board.add_fixed_meshline_policy(axis, coord); + parser.domain_params.input_fixed_meshlines.clear(); return parser.output(); } @@ -317,11 +668,21 @@ expected ParserFromCsx::parse() { return unexpected(res.description()); } + if(parser_params.read_oemsh_params) { + pugi::xpath_node oemsh = doc.select_node("/OpenEMSH"); + TRY(pimpl->parse_oemsh(oemsh.node())); + } + + if(!doc.select_node("/openEMS")) + return unexpected("No \"/openEMS\" path in CSX XML file"); + pugi::xpath_node fdtd = doc.select_node("/openEMS/FDTD"); pugi::xpath_node csx = doc.select_node("/openEMS/ContinuousStructure"); TRY(pimpl->parse_grid(csx.node())); + pimpl->board.set_background_material(pimpl->parse_property(csx.node().child("BackgroundMaterial"))); + { // Primitives' IDs grow disregarding properties. size_t id = 0; @@ -351,6 +712,26 @@ expected ParserFromCsx::parse() { } bar.complete(); domain_params = std::move(pimpl->domain_params); + + if(!pimpl->warning_unsupported_properties_names.empty() + && !pimpl->warning_unsupported_properties_types.empty()) + log({ + .level = Logger::Level::WARNING, + .user_actions = { Logger::UserAction::OK }, + .message = "Unsupported CSXCAD Properties:", + .informative = pimpl->warning_unsupported_properties_types | views::join_with(", "s) | ranges::to(), + .details = pimpl->warning_unsupported_properties_names | views::join_with(", "s) | ranges::to() + }); + if(!pimpl->warning_unsupported_primitives_names.empty() + && !pimpl->warning_unsupported_primitives_types.empty()) + log({ + .level = Logger::Level::WARNING, + .user_actions = { Logger::UserAction::OK }, + .message = "Unsupported CSXCAD Primitives:", + .informative = pimpl->warning_unsupported_primitives_types | views::join_with(", "s) | ranges::to(), + .details = pimpl->warning_unsupported_primitives_names | views::join_with(", "s) | ranges::to() + }); + return {}; } diff --git a/src/infra/parsers/parser_from_csx.hpp b/src/infra/parsers/parser_from_csx.hpp index 8f20bda6..63d56bdb 100644 --- a/src/infra/parsers/parser_from_csx.hpp +++ b/src/infra/parsers/parser_from_csx.hpp @@ -28,6 +28,8 @@ class ParserFromCsx { bool with_yz = true; bool with_zx = true; bool with_xy = true; + bool read_oemsh_params = true; + bool keep_old_mesh = false; }; ~ParserFromCsx(); diff --git a/src/infra/utils/to_string.cpp b/src/infra/utils/to_string.cpp index 30de71fb..64dbd007 100644 --- a/src/infra/utils/to_string.cpp +++ b/src/infra/utils/to_string.cpp @@ -101,6 +101,7 @@ string to_string(Plane const plane) noexcept { //****************************************************************************** string to_string(domain::Material::Type type) noexcept { switch(type) { + case Material::Type::PORT: return "PORT"; case Material::Type::CONDUCTOR: return "CONDUCTOR"; case Material::Type::DIELECTRIC: return "DIELECTRIC"; case Material::Type::AIR: return "AIR"; diff --git a/src/main.cpp b/src/main.cpp index 199515d6..ad15b508 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include "app/openemsh.hpp" #include "ui/cli/cli.hpp" +#include "ui/cli/logger.hpp" #include "ui/cli/progress.hpp" #include "ui/qt/main_window.hpp" @@ -29,40 +30,46 @@ int main(int argc, char* argv[]) { message)); }); + Logger::singleton().register_sink(Logger::id("Cli"), std::make_unique(oemsh.get_params().verbose)); + if(!oemsh.get_params().gui) { if(auto res = oemsh.parse(); !res.has_value()) { - std::cerr - << std::format( - "Error parsing file \"{}\" : {}", + log({ + .level = Logger::Level::ERROR, + .message = std::format( + "Failed to parse file \"{}\" : {}", oemsh.get_params().input.generic_string(), res.error()) - << std::endl; + }); return EXIT_FAILURE; } if(oemsh.is_about_overwriting()) { - std::cerr - << std::format( - "Error: You are about overwriting the file \"{}\", " + log({ + .level = Logger::Level::ERROR, + .message = std::format( + "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(); if(auto res = oemsh.write(); !res.has_value()) { - std::cerr - << std::format( - "Error saving file \"{}\" : {}", + log({ + .level = Logger::Level::ERROR, + .message = std::format( + "Failed to save file \"{}\" : {}", oemsh.get_params().output.generic_string(), res.error()) - << std::endl; + }); return EXIT_FAILURE; - } else if(oemsh.get_params().verbose) { - std::cerr - << std::format( + } else { + log({ + .level = Logger::Level::INFO, + .message = std::format( "Saved file \"{}\"", oemsh.get_params().output.generic_string()) - << std::endl; + }); } } else { QApplication a(argc, argv); diff --git a/src/ui/cli/cli.cpp b/src/ui/cli/cli.cpp index 0c4fdf1c..ca0afeb3 100644 --- a/src/ui/cli/cli.cpp +++ b/src/ui/cli/cli.cpp @@ -133,17 +133,37 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { 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 { + static std::map> const output_formats { { "csx", app::OpenEMSH::Params::OutputFormat::CSX }, { "plantuml", app::OpenEMSH::Params::OutputFormat::PLANTUML }, { "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)))->default_str(reverse_kv(map).at(params.output_format)); + app.add_option("--output-format", params.output_format, "Output format.")->transform(CLI::CheckedTransformer(output_formats, CLI::ignore_case).description(CLI::detail::generate_map(CLI::detail::smart_deref(output_formats), true)))->default_str(reverse_kv(output_formats).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"); app.add_flag("--no-xy", [¶ms](size_t) { params.with_xy = false; }, "Don't process XY plane.")->group("Input options"); + app.add_option("--read-oemsh-params", params.read_oemsh_params, "Read OpenEMSH parameters from file, if any.")->group("Input options")->default_str(to_string(params.read_oemsh_params)); + app.add_option("--integrate-old-mesh", params.keep_old_mesh, "Keep current meshlines and integrate those in the final mesh.")->group("Input options")->default_str(to_string(params.keep_old_mesh)); + + static std::map> const axes { + { "x", domain::Axis::X }, + { "y", domain::Axis::Y }, + { "z", domain::Axis::Z } + }; + app.add_option_function("--add-fixed-meshline", + make_overrider<&domain::Params::input_fixed_meshlines>(domain_overrides), + "Add MeshlinePolicy at fixed position." + )->group("Mesher options") + ->take_all() + ->delimiter(',') + ->transform(CLI::CheckedTransformer(axes, CLI::ignore_case).application_index(0).description("")) + ->type_name("["s + + CLI::detail::type_name() + ":" + + CLI::detail::generate_map(CLI::detail::smart_deref(axes), true) + "," + + CLI::detail::type_name() + + "]"); app.add_option_function("--proximity-limit", make_overrider<&domain::Params::proximity_limit>(domain_overrides), diff --git a/src/ui/cli/logger.cpp b/src/ui/cli/logger.cpp new file mode 100644 index 00000000..1efaa4a7 --- /dev/null +++ b/src/ui/cli/logger.cpp @@ -0,0 +1,112 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#include +#include +#include +#include + +#include "utils/unreachable.hpp" + +#include "logger.hpp" + +using namespace std; + +namespace ui::cli { + +//****************************************************************************** +string to_string(Logger::Level level) noexcept { + switch(level) { + case Logger::Level::QUESTION: return "Question"; + case Logger::Level::INFO: return "Info"; + case Logger::Level::WARNING: return "Warning"; + case Logger::Level::ERROR: return "Error"; + default: ::unreachable(); + } +} + +//****************************************************************************** +string to_emoji(Logger::Level level) noexcept { + switch(level) { +// case Logger::Level::QUESTION: return "❔"; + case Logger::Level::QUESTION: return "❓"; + case Logger::Level::INFO: return "ℹ️ "; + case Logger::Level::WARNING: return "⚠️ "; + case Logger::Level::ERROR: return "⛔"; +// case Logger::Level::QUESTION: return "\u2754"; +// case Logger::Level::QUESTION: return "\u2753"; +// case Logger::Level::INFO: return "\u2139"; +// case Logger::Level::WARNING: return "\u26A0"; +// case Logger::Level::ERROR: return "\u26D4"; + default: ::unreachable(); + } +} + +//****************************************************************************** +string to_string(Logger::UserAction action) noexcept { + switch(action) { + case Logger::UserAction::NOTHING: return ""; + case Logger::UserAction::CANCEL: return "n"; + case Logger::UserAction::OK: return "y"; + case Logger::UserAction::SAVE: return "s"; + case Logger::UserAction::ABORT: return "a"; + default: ::unreachable(); + } +} + +//****************************************************************************** +string to_string(set const& actions) noexcept { + return format("[{}]", + actions + | views::transform([](auto const& action) { return to_string(action); }) + | views::join_with("/"s) + | ranges::to()); +} + +//****************************************************************************** +Logger::UserAction from_string(string const& str) noexcept { + if(str == "n") return Logger::UserAction::CANCEL; + else if(str == "y") return Logger::UserAction::OK; + else if(str == "s") return Logger::UserAction::SAVE; + else if(str == "a") return Logger::UserAction::ABORT; + else return Logger::UserAction::NOTHING; +} + +//****************************************************************************** +LoggerSink::LoggerSink(bool verbose) +: verbose(verbose) +{} + +//****************************************************************************** +Logger::UserAction LoggerSink::log(Logger::LogEvent const& log) const { + if(!(log.level == Logger::Level::INFO && !verbose)) + print(cerr, + "{} {}: {}{}{}", + to_emoji(log.level), + to_string(log.level), + log.message, + (log.informative.empty() ? string() : format(" {}", log.informative)), + (log.details.empty() ? string() : format("\n{}", log.details))); + + if(is_interactive + && log.level == Logger::Level::QUESTION + && !log.user_actions.empty()) { + Logger::UserAction res = Logger::UserAction::NOTHING; + do { + print(cerr, " {} ? ", to_string(log.user_actions)); + string ln; + getline(cin, ln); + res = from_string(ln); + } while(res == Logger::UserAction::NOTHING); + return res; + } else { + if(!(log.level == Logger::Level::INFO && !verbose)) + println(cerr); + return Logger::UserAction::NOTHING; + } +} + +} // namespace ui::cli diff --git a/src/ui/cli/logger.hpp b/src/ui/cli/logger.hpp new file mode 100644 index 00000000..20fd6957 --- /dev/null +++ b/src/ui/cli/logger.hpp @@ -0,0 +1,24 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include "utils/logger.hpp" + +namespace ui::cli { + +//****************************************************************************** +class LoggerSink final : public Logger::ISink { +public: + explicit LoggerSink(bool verbose); + Logger::UserAction log(Logger::LogEvent const& log) const override; + +private: + bool verbose; +}; + + +} // namespace ui::cli diff --git a/src/ui/qt/edit/edit_model_meshline_policy.cpp b/src/ui/qt/edit/edit_model_meshline_policy.cpp index 2541a2c9..4edc10c6 100644 --- a/src/ui/qt/edit/edit_model_meshline_policy.cpp +++ b/src/ui/qt/edit/edit_model_meshline_policy.cpp @@ -39,7 +39,6 @@ EditModelMeshlinePolicy::EditModelMeshlinePolicy(domain::MeshlinePolicy* meshlin "Direction associated with Policy."); make_row(2, "Enabled", state.is_enabled, "Take into account in the meshing process."); -// make_row(3, "res_factor", QString::number(state.res_factor), ""); make_row(3, "d", QString::number(state.d), "Desired distance between policy lines (HALFS|THIRDS) or " "between policy line and adjacent lines (ONELINE).
" @@ -77,7 +76,6 @@ void EditModelMeshlinePolicy::commit() { std::array does_succeed = { are_policy_and_normal_compatible(), try_to_bool(item(2, V)->checkState(), state.is_enabled), -// try_to_double(item(3, V)->text(), state.res_factor), try_to_double(item(3, V)->text(), state.d) }; diff --git a/src/ui/qt/logger.cpp b/src/ui/qt/logger.cpp new file mode 100644 index 00000000..ba15f8e5 --- /dev/null +++ b/src/ui/qt/logger.cpp @@ -0,0 +1,89 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#include +#include + +#include "utils/unreachable.hpp" + +#include "logger.hpp" + +using namespace std; + +namespace ui::qt { + +//****************************************************************************** +Logger::UserAction from_qt(int button) noexcept { + switch(static_cast(button)) { + default: [[fallthrough]]; + case QMessageBox::NoButton: return Logger::UserAction::NOTHING; + case QMessageBox::Cancel: return Logger::UserAction::CANCEL; + case QMessageBox::Ok: return Logger::UserAction::OK; + case QMessageBox::Save: return Logger::UserAction::SAVE; + case QMessageBox::Abort: return Logger::UserAction::ABORT; + } +} + +//****************************************************************************** +QMessageBox::StandardButton to_qt(Logger::UserAction action) noexcept { + switch(action) { + case Logger::UserAction::NOTHING: return QMessageBox::NoButton; + case Logger::UserAction::CANCEL: return QMessageBox::Cancel; + case Logger::UserAction::OK: return QMessageBox::Ok; + case Logger::UserAction::SAVE: return QMessageBox::Save; + case Logger::UserAction::ABORT: return QMessageBox::Abort; + default: ::unreachable(); + } +} + +//****************************************************************************** +QMessageBox::StandardButtons to_qt(set const& actions) noexcept { + QMessageBox::StandardButtons buttons = QMessageBox::NoButton; + for(auto const action : actions) + buttons |= to_qt(action); + return buttons; +} + +//****************************************************************************** +QMessageBox::Icon to_qt(Logger::Level level) noexcept { + switch(level) { + case Logger::Level::QUESTION: return QMessageBox::Question; + case Logger::Level::INFO: return QMessageBox::Information; + case Logger::Level::WARNING: return QMessageBox::Warning; + case Logger::Level::ERROR: return QMessageBox::Critical; + default: ::unreachable(); + } +} + +//****************************************************************************** +LoggerSink::LoggerSink(QStatusBar* status_bar, QWidget* parent) +: status_bar(status_bar) +, parent(parent) +{} + +//****************************************************************************** +Logger::UserAction LoggerSink::log(Logger::LogEvent const& log) const { + if(log.level != Logger::Level::QUESTION + && (log.user_actions.empty() || log.user_actions.contains(Logger::UserAction::NOTHING))) { + status_bar->showMessage(QString::fromStdString(log.message)); + return Logger::UserAction::NOTHING; + } else { + QMessageBox message(parent); + message.setText(QString::fromStdString(log.message)); + message.setInformativeText(QString::fromStdString(log.informative)); + message.setDetailedText(QString::fromStdString(log.details)); + message.setIcon(to_qt(log.level)); + message.setStandardButtons(is_interactive + ? to_qt(log.user_actions) + : QMessageBox::Ignore); + if(log.default_user_action.has_value()) + message.setDefaultButton(to_qt(log.default_user_action.value())); + + return from_qt(message.exec()); + } +} + +} // namespace ui::qt diff --git a/src/ui/qt/logger.hpp b/src/ui/qt/logger.hpp new file mode 100644 index 00000000..60cf7354 --- /dev/null +++ b/src/ui/qt/logger.hpp @@ -0,0 +1,26 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include "utils/logger.hpp" + +class QStatusBar; +class QWidget; + +namespace ui::qt { + +//****************************************************************************** +class LoggerSink final : public Logger::ISink { +private: + QStatusBar* status_bar; + QWidget* parent; +public: + LoggerSink(QStatusBar* status_bar, QWidget* parent); + Logger::UserAction log(Logger::LogEvent const& log) const override; +}; + +} // namespace ui::qt diff --git a/src/ui/qt/main_window.cpp b/src/ui/qt/main_window.cpp index 9fd79a06..7fe2ff0e 100644 --- a/src/ui/qt/main_window.cpp +++ b/src/ui/qt/main_window.cpp @@ -22,6 +22,7 @@ #include "utils/state_management.hpp" #include "utils/unreachable.hpp" #include "about_dialog.hpp" +#include "logger.hpp" #include "progress.hpp" #include "settings.hpp" @@ -60,6 +61,8 @@ MainWindow::MainWindow(app::OpenEMSH& oemsh, QWidget* parent) message))); }); + Logger::singleton().register_sink(Logger::id("Qt"), std::make_unique(ui->statusBar, this), true); + for(auto const& style : Style::available_styles) { auto* const action = new QAction(style.name, ui->ag_styles); action->setCheckable(true); @@ -88,7 +91,14 @@ bool MainWindow::parse_and_display() { if(auto res = oemsh.parse() ; !res.has_value()) { QGuiApplication::restoreOverrideCursor(); - ui->statusBar->showMessage("Error parsing file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); + log({ + .level = Logger::Level::ERROR, + .user_actions = { Logger::UserAction::OK }, + .message = std::format( + "Failed to parse file \"{}\" : {}", + csx_file.toStdString(), + res.error()) + }); return false; } @@ -325,13 +335,15 @@ void MainWindow::save_csx_file() { oemsh.set_output_format(app::OpenEMSH::Params::OutputFormat::CSX); if(oemsh.is_about_overwriting()) { - auto res = QMessageBox::warning(this, - "", - 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) { + auto res = log({ + .level = Logger::Level::WARNING, + .user_actions = { Logger::UserAction::CANCEL, Logger::UserAction::SAVE }, + .message = std::format( + "You are about overwriting the file \"{}\", " + "do you want to continue?", + oemsh.get_params().output.generic_string()) + }); + if(res != Logger::UserAction::SAVE) { return; } } @@ -339,9 +351,23 @@ void MainWindow::save_csx_file() { QGuiApplication::setOverrideCursor(Qt::WaitCursor); if(auto res = oemsh.write() ; res.has_value()) - ui->statusBar->showMessage("Saved file \"" + csx_file + "\""); + log({ + .level = Logger::Level::INFO, + .message = std::format( + "Saved file \"{}\"", + csx_file.toStdString()) + }); else - ui->statusBar->showMessage("Failed to save file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); + log({ + .level = Logger::Level::ERROR, + .user_actions = { Logger::UserAction::OK }, + .message = std::format( + "Failed to save file \"{}\" : {}", + csx_file.toStdString(), + res.error()) + }); + + QGuiApplication::restoreOverrideCursor(); } @@ -350,14 +376,15 @@ void MainWindow::on_a_file_save_triggered() { if(oemsh.get_params().output.empty()) { oemsh.set_output(csx_file.toStdString()); } else if(csx_file != QString::fromStdString(oemsh.get_params().output.generic_string())) { - auto res = QMessageBox::question(this, - "", - QString::fromStdString(std::format( + auto res = log({ + .level = Logger::Level::QUESTION, + .user_actions = { Logger::UserAction::CANCEL, Logger::UserAction::SAVE }, + .message = 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) { + oemsh.get_params().input.generic_string()) + }); + if(res == Logger::UserAction::SAVE) { csx_file = QString::fromStdString(oemsh.get_params().output.generic_string()); } else { return; diff --git a/src/utils/logger.cpp b/src/utils/logger.cpp new file mode 100644 index 00000000..13e994fc --- /dev/null +++ b/src/utils/logger.cpp @@ -0,0 +1,64 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#include + +#include "logger.hpp" + +using namespace std; + +//****************************************************************************** +Logger::Id Logger::id(string_view const& id) { + return hash{}(id); +} + +//****************************************************************************** +void Logger::ISink::set_interactive(bool is_the_interactive) { + is_interactive = is_the_interactive; +} + +//****************************************************************************** +Logger& Logger::singleton() { + static Logger logger; + return logger; +} + +//****************************************************************************** +void Logger::set_as_the_interactive(Id id) { + if(id == 0) { + interactive_sink = nullptr; + for(auto& [_, sink] : sinks) + sink->set_interactive(false); + } else if(sinks.contains(id)) { + interactive_sink = sinks.at(id).get(); + interactive_sink->set_interactive(true); + for(auto& [_, sink] : sinks) + if(sink.get() != interactive_sink) + sink->set_interactive(false); + } +} + +//****************************************************************************** +void Logger::register_sink(Id id, unique_ptr&& sink, bool is_the_interactive) { + sinks.emplace(id, std::move(sink)); + if(is_the_interactive) + set_as_the_interactive(id); +} + +//****************************************************************************** +Logger::UserAction Logger::log(LogEvent const& log) const { + if(log.destination && sinks.contains(log.destination)) { + return sinks.at(log.destination)->log(log); + } else { + UserAction ret = UserAction::NOTHING; + for(auto const& [_, sink] : sinks) { + if(auto res = sink->log(log) + ; sink.get() == interactive_sink) + ret = res; + } + return ret; + } +} diff --git a/src/utils/logger.hpp b/src/utils/logger.hpp new file mode 100644 index 00000000..665134c7 --- /dev/null +++ b/src/utils/logger.hpp @@ -0,0 +1,76 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include +#include +#include +#include +#include + +//****************************************************************************** +class Logger { +public: + // https://doc.qt.io/qt-6/qmessagebox.html#Icon-enum + enum class Level { + QUESTION, + INFO, + WARNING, + ERROR + }; + + // https://doc.qt.io/qt-6/qmessagebox.html#StandardButton-enum + enum class UserAction { + NOTHING, + CANCEL, + OK, + SAVE, + ABORT + }; + + using Id = std::size_t; + + struct LogEvent { + Id destination = 0; + Level level; + std::set user_actions; + std::optional default_user_action; + std::string message; + std::string informative; + std::string details; + }; + + class ISink { + public: + virtual ~ISink() = default; + virtual UserAction log(LogEvent const& log) const = 0; + friend Logger; + private: + void set_interactive(bool is_the_interactive); + protected: + bool is_interactive = false; + }; + + static Id id(std::string_view const& id); + + static Logger& singleton(); + + void set_as_the_interactive(Id id); + + void register_sink(Id id, std::unique_ptr&& sink, bool is_the_interactive = false); + + UserAction log(LogEvent const& log) const; + +private: + ISink* interactive_sink = nullptr; + std::map> sinks; +}; + +//****************************************************************************** +inline Logger::UserAction log(Logger::LogEvent const& log) { + return Logger::singleton().log(log); +} diff --git a/test/shapes.csx b/test/shapes.csx new file mode 100644 index 00000000..2cdd24e2 --- /dev/null +++ b/test/shapes.csx @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/unit/domain/conflicts/test_conflict_too_close_meshline_policies.cpp b/test/unit/domain/conflicts/test_conflict_too_close_meshline_policies.cpp index 9986e3b0..905fab9c 100644 --- a/test/unit/domain/conflicts/test_conflict_too_close_meshline_policies.cpp +++ b/test/unit/domain/conflicts/test_conflict_too_close_meshline_policies.cpp @@ -309,16 +309,62 @@ SCENARIO("void ConflictTooCloseMeshlinePolicies::auto_solve(MeshlinePolicyManage 11, t); ConflictTooCloseMeshlinePolicies x(X, &a, &c, t); - ConflictTooCloseMeshlinePolicies y(X, &b, &c, t); - THEN("Should add nothing to the meshline policy manager") { + ConflictTooCloseMeshlinePolicies y(X, &c, &b, t); + THEN("Should add a ONELINE meshline policy at the coord of the ONELINE in the meshline policy manager") { x.auto_solve(mpm); y.auto_solve(mpm); - REQUIRE(mpm.get_current_state().line_policies[X].size() == 0); REQUIRE(mpm.get_current_state().line_policies[Y].size() == 0); - REQUIRE_FALSE(x.get_current_state().is_solved); - REQUIRE_FALSE(y.get_current_state().is_solved); - REQUIRE(x.get_current_state().solution == nullptr); - REQUIRE(y.get_current_state().solution == nullptr); + REQUIRE(mpm.get_current_state().line_policies[X].size() == 2); + REQUIRE(mpm.get_current_state().line_policies[X][0].get() == x.get_current_state().solution); + REQUIRE(mpm.get_current_state().line_policies[X][0]->get_current_state().origins.size() == 1); + REQUIRE(mpm.get_current_state().line_policies[X][0]->get_current_state().origins[0] == &x); + REQUIRE(mpm.get_current_state().line_policies[X][0]->get_current_state().policy == MeshlinePolicy::Policy::ONELINE); + REQUIRE(mpm.get_current_state().line_policies[X][0]->get_current_state().normal == MeshlinePolicy::Normal::NONE); + REQUIRE(mpm.get_current_state().line_policies[X][0]->coord == 11); + REQUIRE(mpm.get_current_state().line_policies[X][0]->get_current_state().is_enabled); + REQUIRE(mpm.get_current_state().line_policies[X][1].get() == y.get_current_state().solution); + REQUIRE(mpm.get_current_state().line_policies[X][1]->get_current_state().origins.size() == 1); + REQUIRE(mpm.get_current_state().line_policies[X][1]->get_current_state().origins[0] == &y); + REQUIRE(mpm.get_current_state().line_policies[X][1]->get_current_state().policy == MeshlinePolicy::Policy::ONELINE); + REQUIRE(mpm.get_current_state().line_policies[X][1]->get_current_state().normal == MeshlinePolicy::Normal::NONE); + REQUIRE(mpm.get_current_state().line_policies[X][1]->coord == 11); + REQUIRE(mpm.get_current_state().line_policies[X][1]->get_current_state().is_enabled); + REQUIRE(x.get_current_state().is_solved); + REQUIRE(y.get_current_state().is_solved); + REQUIRE(x.get_current_state().solution == mpm.get_current_state().line_policies[X][0].get()); + REQUIRE(y.get_current_state().solution == mpm.get_current_state().line_policies[X][1].get()); + } + } + + GIVEN("A conflict between ONELINE and ONELINE meshline policies") { + MeshlinePolicy a( + Y, + MeshlinePolicy::Policy::ONELINE, + MeshlinePolicy::Normal::NONE, + ¶ms, + 10, + t); + MeshlinePolicy b( + Y, + MeshlinePolicy::Policy::ONELINE, + MeshlinePolicy::Normal::NONE, + ¶ms, + 11, + t); + ConflictTooCloseMeshlinePolicies x(Y, &a, &b, t); + THEN("Should add a ONELINE meshline policy in the meshline policy manager") { + x.auto_solve(mpm); + REQUIRE(mpm.get_current_state().line_policies[X].size() == 0); + REQUIRE(mpm.get_current_state().line_policies[Y].size() == 1); + REQUIRE(mpm.get_current_state().line_policies[Y][0].get() == x.get_current_state().solution); + REQUIRE(mpm.get_current_state().line_policies[Y][0]->get_current_state().origins.size() == 1); + REQUIRE(mpm.get_current_state().line_policies[Y][0]->get_current_state().origins[0] == &x); + REQUIRE(mpm.get_current_state().line_policies[Y][0]->get_current_state().policy == MeshlinePolicy::Policy::ONELINE); + REQUIRE(mpm.get_current_state().line_policies[Y][0]->get_current_state().normal == MeshlinePolicy::Normal::NONE); + REQUIRE(mpm.get_current_state().line_policies[Y][0]->coord == 10.5); + REQUIRE(mpm.get_current_state().line_policies[Y][0]->get_current_state().is_enabled); + REQUIRE(x.get_current_state().is_solved); + REQUIRE(x.get_current_state().solution == mpm.get_current_state().line_policies[Y][0].get()); } } } diff --git a/test/unit/domain/test_board.cpp b/test/unit/domain/test_board.cpp index 42e3157b..981d3a60 100644 --- a/test/unit/domain/test_board.cpp +++ b/test/unit/domain/test_board.cpp @@ -250,10 +250,12 @@ SCENARIO("std::unique_ptr Board::Builder::build()", "[board]") { { 66.2713, -43.9276 }, { 66.2713, -43.9514 }, { 70.3673, -43.9514 }}); + b.set_background_material(material); REQUIRE(b.polygons[XY].size() == 3); WHEN("Calling build()") { std::shared_ptr a = b.build(); THEN("Should output a Board containing the polygons") { + REQUIRE(a->material == material); REQUIRE(a->get_current_state().polygons[XY].size() == 3); REQUIRE(a->get_current_state().polygons[XY][0]->name == "MS1"); REQUIRE(*(a->get_current_state().polygons[XY][0]->points[0]) == Point(16.1, -26.5)); @@ -309,21 +311,41 @@ SCENARIO("std::pair, std::remove_const_t(XY, c2, "", 2, z, from_init_list({{ 1, 1 }, { 6, 1 }, { 6, 6 }, { 1, 6 }}), t); auto pd2 = std::make_shared(XY, d2, "", 2, z, from_init_list({{ 1, 1 }, { 6, 1 }, { 6, 6 }, { 1, 6 }}), t); auto pa2 = std::make_shared(XY, a2, "", 2, z, from_init_list({{ 1, 1 }, { 6, 1 }, { 6, 6 }, { 1, 6 }}), t); - WHEN("Looking for ambient Material inside the overlap of all Polygons relative to one of AIR Material with the lower priority") { - { - PlaneSpace>> tmp; - tmp[XY].push_back(px); - tmp[XY].push_back(pc0); - tmp[XY].push_back(pd0); - tmp[XY].push_back(pa0); - tmp[XY].push_back(pc1); - tmp[XY].push_back(pd1); - tmp[XY].push_back(pa1); - tmp[XY].push_back(pc2); - tmp[XY].push_back(pd2); - tmp[XY].push_back(pa2); - b = std::make_unique(std::move(tmp), Params(), t); + { + PlaneSpace>> tmp; + tmp[XY].push_back(px); + tmp[XY].push_back(pc0); + tmp[XY].push_back(pd0); + tmp[XY].push_back(pa0); + tmp[XY].push_back(pc1); + tmp[XY].push_back(pd1); + tmp[XY].push_back(pa1); + tmp[XY].push_back(pc2); + tmp[XY].push_back(pd2); + tmp[XY].push_back(pa2); + b = std::make_unique(std::move(tmp), Params(), t); + } + WHEN("Looking for ambient Material outside of any Polygon, relative to a Polygon") { + AND_WHEN("The board has a background Material") { + auto background = std::make_shared(Material::Type::DIELECTRIC, ""); + b->material = background; + auto [material, priority] = b->find_ambient_material(XY, Range({ 10, 10 }, { 11, 11 }), px); + THEN("Should return the board background Material and the lowest possible priority") { + REQUIRE(material); + REQUIRE(material == background); + REQUIRE(material->type == Material::Type::DIELECTRIC); + REQUIRE(priority == std::numeric_limits::min()); + } + } + AND_WHEN("The board has no background Material") { + auto [material, priority] = b->find_ambient_material(XY, Range({ 10, 10 }, { 11, 11 }), px); + THEN("Should not return any Material and the lowest possible priority") { + REQUIRE_FALSE(material); + REQUIRE(priority == std::numeric_limits::min()); + } } + } + WHEN("Looking for ambient Material inside the overlap of all Polygons relative to one of AIR Material with the lower priority") { auto [material, priority] = b->find_ambient_material(XY, Range({ 3.5, 3.2 }, { 3.5, 3.8 }), px); THEN("Should the CONDUCTOR Material of the Polygon with the highest priority") { REQUIRE(material); @@ -352,8 +374,20 @@ SCENARIO("std::pair, std::remove_const_t(std::move(tmp), Params(), t); } - THEN("Should not return any Material") { - REQUIRE_FALSE(b->find_ambient_material(XY, Range({ 10, 10 }, { 11, 11 }))); + AND_WHEN("The board has a background Material") { + auto background = std::make_shared(Material::Type::DIELECTRIC, ""); + b->material = background; + auto material = b->find_ambient_material(XY, Range({ 10, 10 }, { 11, 11 })); + THEN("Should return the board background Material") { + REQUIRE(material); + REQUIRE(material == background); + REQUIRE(material->type == Material::Type::DIELECTRIC); + } + } + AND_WHEN("The board has no background Material") { + THEN("Should not return any Material") { + REQUIRE_FALSE(b->find_ambient_material(XY, Range({ 10, 10 }, { 11, 11 }))); + } } } WHEN("Looking for absolute ambient Material inside the overlap of all Polygons") { diff --git a/test/unit/domain/test_material.cpp b/test/unit/domain/test_material.cpp index eec2ef60..a71f56f2 100644 --- a/test/unit/domain/test_material.cpp +++ b/test/unit/domain/test_material.cpp @@ -16,9 +16,35 @@ using namespace domain; //****************************************************************************** SCENARIO("std::strong_ordering Material::operator<=>(Material const& other) const noexcept", "[domain][material]") { + Material port(Material::Type::PORT, ""); Material conductor(Material::Type::CONDUCTOR, ""); Material dielectric(Material::Type::DIELECTRIC, ""); Material air(Material::Type::AIR, ""); + WHEN("Comparing PORT to PORT") { + THEN("Should return equivalent") { + REQUIRE(port <=> port == std::strong_ordering::equivalent); + } + } + WHEN("Comparing PORT to CONDUCTOR") { + THEN("Should return greater") { + REQUIRE(port <=> conductor == std::strong_ordering::greater); + } + } + WHEN("Comparing PORT to DIELECTRIC") { + THEN("Should return greater") { + REQUIRE(port <=> dielectric == std::strong_ordering::greater); + } + } + WHEN("Comparing PORT to AIR") { + THEN("Should return greater") { + REQUIRE(port <=> air == std::strong_ordering::greater); + } + } + WHEN("Comparing CONDUCTOR to PORT") { + THEN("Should return equivalent") { + REQUIRE(conductor <=> port == std::strong_ordering::less); + } + } WHEN("Comparing CONDUCTOR to CONDUCTOR") { THEN("Should return equivalent") { REQUIRE(conductor <=> conductor == std::strong_ordering::equivalent); @@ -39,6 +65,11 @@ SCENARIO("std::strong_ordering Material::operator<=>(Material const& other) cons REQUIRE(dielectric <=> conductor == std::strong_ordering::less); } } + WHEN("Comparing DIELECTRIC to PORT") { + THEN("Should return less") { + REQUIRE(dielectric <=> port == std::strong_ordering::less); + } + } WHEN("Comparing DIELECTRIC to DIELECTRIC") { THEN("Should return equivalent") { REQUIRE(dielectric <=> dielectric == std::strong_ordering::equivalent); @@ -49,6 +80,11 @@ SCENARIO("std::strong_ordering Material::operator<=>(Material const& other) cons REQUIRE(dielectric <=> air == std::strong_ordering::greater); } } + WHEN("Comparing AIR to PORT") { + THEN("Should return less") { + REQUIRE(air <=> port == std::strong_ordering::less); + } + } WHEN("Comparing AIR to CONDUCTOR") { THEN("Should return less") { REQUIRE(air <=> conductor == std::strong_ordering::less); diff --git a/test/unit/domain/test_meshline_policy_manager.cpp b/test/unit/domain/test_meshline_policy_manager.cpp index b9b236b3..d9f774a1 100644 --- a/test/unit/domain/test_meshline_policy_manager.cpp +++ b/test/unit/domain/test_meshline_policy_manager.cpp @@ -298,29 +298,6 @@ Coord proximity_limit)", "[meshline_policy_manager]") { REQUIRE_FALSE(ret.has_value()); } } - - WHEN("Containing two meshline policies less distant than proximity limit but one is ONELINE policy") { - MeshlinePolicy a( - Y, - MeshlinePolicy::Policy::ONELINE, - MeshlinePolicy::Normal::NONE, - ¶ms, - 10, - t); - MeshlinePolicy b( - Y, - MeshlinePolicy::Policy::HALFS, - MeshlinePolicy::Normal::NONE, - ¶ms, - 10.5, - t); - vec.emplace_back(&a); - vec.emplace_back(&b); - THEN("Should return nullopt") { - auto ret = detect_closest_meshline_policies(vec, params.get_current_state().proximity_limit); - REQUIRE_FALSE(ret.has_value()); - } - } } }