From 6a54591bac6c8843f12c6971623437083d017ab3 Mon Sep 17 00:00:00 2001 From: Andrei Pozolotin Date: Tue, 2 Jan 2024 09:33:38 -0600 Subject: [PATCH] Resolve #11825 - no automatic quantity conversion: `App::anyToQuantity` --- src/App/Expression.cpp | 28 +++++++++++++++++++++++ src/App/Expression.h | 1 + tests/src/App/Expression.cpp | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index 75ac7e2e37..f62ff8c814 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -517,6 +518,29 @@ Py::Object pyFromQuantity(const Quantity &quantity) { } } +static const std::regex REGEX_QUANTITY( + R"(\s*)" + R"(([-+]?[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?))" // value + R"(\s*)" + R"(([^\s]+)?)" // unit + R"(\s*)" +); + +// https://github.com/FreeCAD/FreeCAD/issues/11825 +Quantity parseQuantityFromText(std::string text) { + std::smatch match; + if (std::regex_match(text, match, REGEX_QUANTITY)) { + std::string value_text = match[1]; + std::string unit_text = match[3]; + double value = std::stod(value_text); + Unit unit = Unit(QString::fromStdString(unit_text)); + return Quantity(value, unit); + } else { + std::string error_message = "Failed to parse to Quantity: text='" + text + "'"; + PARSER_THROW(error_message); + } +} + Quantity anyToQuantity(const App::any &value, const char *msg) { if (is_type(value,typeid(Quantity))) { return cast(value); @@ -530,6 +554,10 @@ Quantity anyToQuantity(const App::any &value, const char *msg) { return Quantity(cast(value)); } else if (is_type(value,typeid(double))) { return Quantity(cast(value)); + } else if (is_type(value,typeid(const char*))) { + return parseQuantityFromText(std::string(cast(value))); + } else if (is_type(value,typeid(std::string))) { + return parseQuantityFromText(cast(value)); } if(!msg) msg = "Failed to convert to Quantity"; diff --git a/src/App/Expression.h b/src/App/Expression.h index 78f1edb1c5..55dc530aef 100644 --- a/src/App/Expression.h +++ b/src/App/Expression.h @@ -49,6 +49,7 @@ using ExpressionPtr = std::unique_ptr; AppExport bool isAnyEqual(const App::any &v1, const App::any &v2); AppExport Base::Quantity anyToQuantity(const App::any &value, const char *errmsg = nullptr); +AppExport Base::Quantity parseQuantityFromText(std::string text); // Map of depending objects to a map of depending property name to the full referencing object identifier using ExpressionDeps = std::map > >; diff --git a/tests/src/App/Expression.cpp b/tests/src/App/Expression.cpp index 8a7c1a50fb..8ac0737b69 100644 --- a/tests/src/App/Expression.cpp +++ b/tests/src/App/Expression.cpp @@ -49,4 +49,47 @@ TEST(Expression, test_e_rad) EXPECT_EQ(op->toString(), "e rad"); op.release(); } + +TEST(Expression, parseQuantityFromText) +{ + EXPECT_ANY_THROW(App::parseQuantityFromText("")) << "should not parse empty"; + EXPECT_ANY_THROW(App::parseQuantityFromText("mm")) << "should not parse missing value"; + EXPECT_NO_THROW(App::parseQuantityFromText("2")) << "ok to parse missing unit"; + EXPECT_NO_THROW(App::parseQuantityFromText("2mm")); + EXPECT_NO_THROW(App::parseQuantityFromText("2 mm")); + EXPECT_NO_THROW(App::parseQuantityFromText("\t \n .5e-3kg/m^3 \t")); + EXPECT_NO_THROW(App::parseQuantityFromText("\n \t -6.7E3 \t A/m^2 \t")); + EXPECT_EQ(App::parseQuantityFromText("2mm"), Base::Quantity(2.0, QString::fromStdString("mm"))); // exact ULP form + EXPECT_EQ(App::parseQuantityFromText("2 mm"), Base::Quantity(2.0, QString::fromStdString("mm"))); // exact ULP form + auto quant_one = App::parseQuantityFromText("\t \n.5e-3kg/m^3 \t"); + EXPECT_DOUBLE_EQ(quant_one.getValue(), 0.5e-3); // approximately equal, to within 4 ULPs + EXPECT_EQ(quant_one.getUnit(), Base::Unit(QString::fromStdString("kg/m^3"))); + auto quant_two = App::parseQuantityFromText("\n \t -6.7E3 \t A/m^2 \t"); + EXPECT_DOUBLE_EQ(quant_two.getValue(), -6.7e+3); // approximately equal, to within 4 ULPs + EXPECT_EQ(quant_two.getUnit(), Base::Unit(QString::fromStdString("A/m^2"))); +} + +TEST(Expression, anyToQuantity) +{ + EXPECT_EQ(App::anyToQuantity(Base::Quantity()), Base::Quantity()); + EXPECT_EQ(App::anyToQuantity(true), Base::Quantity(1.0)); + EXPECT_EQ(App::anyToQuantity(false), Base::Quantity(0.0)); + EXPECT_EQ(App::anyToQuantity(123), Base::Quantity(123.0)); + EXPECT_EQ(App::anyToQuantity(123L), Base::Quantity(123.0)); + EXPECT_EQ(App::anyToQuantity(123.0F), Base::Quantity(123.0)); + EXPECT_EQ(App::anyToQuantity(123.0), Base::Quantity(123.0)); + EXPECT_EQ(App::anyToQuantity("123"), Base::Quantity(123.0)); + EXPECT_EQ(App::anyToQuantity(std::string("123")), Base::Quantity(123.0)); + EXPECT_EQ(App::anyToQuantity("123 mm"), Base::Quantity(123.0, QString::fromStdString("mm"))); + EXPECT_EQ(App::anyToQuantity(std::string("123 mm")), Base::Quantity(123.0, QString::fromStdString("mm"))); + EXPECT_ANY_THROW(App::anyToQuantity("")); + EXPECT_ANY_THROW(App::anyToQuantity("mm")); +} + +TEST(Expression, isAnyEqual) +{ + EXPECT_TRUE(App::isAnyEqual("123 mm", "123 mm")); + EXPECT_TRUE(App::isAnyEqual("123 mm", Base::Quantity(123.0, QString::fromStdString("mm")))); + EXPECT_TRUE(App::isAnyEqual(Base::Quantity(123.0, QString::fromStdString("mm")), "123 mm")); +} // clang-format on