From 19bb0a5d5273f08aff62c64bb3513408d0f8c62c Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 28 Jan 2025 14:14:32 +0100 Subject: [PATCH 01/11] Test: Add test case for UnitsSchemaMeterDecimal --- tests/src/Base/SchemaTests.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/src/Base/SchemaTests.cpp b/tests/src/Base/SchemaTests.cpp index 412bc8ac81..1c36444e2a 100644 --- a/tests/src/Base/SchemaTests.cpp +++ b/tests/src/Base/SchemaTests.cpp @@ -76,6 +76,14 @@ protected: std::unique_ptr schemas; // NOLINT }; +TEST_F(SchemaTest, meter_decimal_1_mm_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 1.0, Unit::Length, 6); + const auto expect {"0.001000 m"}; + + EXPECT_EQ(result, expect); +} + TEST_F(SchemaTest, imperial_decimal_1_mm_default_precision) { const std::string result = set("ImperialDecimal", Unit::Length, 1.0); From e5e251df8f2d9dbfb84009505caf775a4dd4bb14 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 1 Feb 2025 01:39:37 +0100 Subject: [PATCH 02/11] Test: Add further test cases for quantities --- tests/src/Base/SchemaTests.cpp | 264 +++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/tests/src/Base/SchemaTests.cpp b/tests/src/Base/SchemaTests.cpp index 1c36444e2a..7ffc09be5d 100644 --- a/tests/src/Base/SchemaTests.cpp +++ b/tests/src/Base/SchemaTests.cpp @@ -29,6 +29,7 @@ #include "Base/UnitsSchemas.h" #include +#include #include using Base::Quantity; @@ -84,6 +85,111 @@ TEST_F(SchemaTest, meter_decimal_1_mm_precision_6) EXPECT_EQ(result, expect); } +TEST_F(SchemaTest, meter_decimal_15_mm2_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 15.0, Unit::Area, 6); + const auto expect {"0.000015 m^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_mm3_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123456000.0, Unit::Volume, 6); + const auto expect {"0.123456 m^3"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_W_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123456000.0, Unit::Power, 6); + const auto expect {"123.456000 W"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_V_precision_6) +{ + const std::string result = + setWithPrecision("MeterDecimal", 123456000.0, Unit::ElectricPotential, 6); + const auto expect {"123.456000 V"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_W_m2_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123.456, Unit::HeatFlux, 6); + const auto expect {"123.456000 W/m^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_m_s_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123.456, Unit::Velocity, 6); + const auto expect {"0.123456 m/s"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_1_mm_precision_6) +{ + const std::string result = setWithPrecision("MKS", 1.0, Unit::Length, 6); + const auto expect {"1.000000 mm"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_15_mm2_precision_6) +{ + const std::string result = setWithPrecision("MKS", 15.0, Unit::Area, 6); + const auto expect {"15.000000 mm^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_mm3_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123456000.0, Unit::Volume, 6); + const auto expect {"123.456000 l"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_W_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123456000.0, Unit::Power, 6); + const auto expect {"123.456000 W"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_V_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123456000.0, Unit::ElectricPotential, 6); + const auto expect {"123.456000 V"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_W_m2_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123.456, Unit::HeatFlux, 6); + const auto expect {"123.456000 W/m^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_m_s_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123.456, Unit::Velocity, 6); + const auto expect {"0.123456 m/s"}; + + EXPECT_EQ(result, expect); +} + TEST_F(SchemaTest, imperial_decimal_1_mm_default_precision) { const std::string result = set("ImperialDecimal", Unit::Length, 1.0); @@ -418,3 +524,161 @@ TEST_F(SchemaTest, unknown_schema_name_throws) { EXPECT_THROW(UnitsApi::setSchema("Unknown"), RuntimeError); } + +TEST_F(SchemaTest, round_trip_test) +{ + const auto units = std::to_array({ + Unit::Length, + Unit::Mass, + Unit::Area, + Unit::Density, + Unit::Volume, + Unit::TimeSpan, + Unit::Frequency, + Unit::Velocity, + Unit::Acceleration, + Unit::Temperature, + Unit::CurrentDensity, + Unit::ElectricCurrent, + Unit::ElectricPotential, + Unit::ElectricCharge, + Unit::SurfaceChargeDensity, + Unit::MagneticFieldStrength, + Unit::MagneticFlux, + Unit::MagneticFluxDensity, + Unit::Magnetization, + Unit::ElectricalCapacitance, + Unit::ElectricalInductance, + Unit::ElectricalConductance, + Unit::ElectricalResistance, + Unit::ElectricalConductivity, + Unit::ElectromagneticPotential, + Unit::AmountOfSubstance, + Unit::LuminousIntensity, + Unit::CompressiveStrength, + Unit::Pressure, + Unit::ShearModulus, + Unit::Stress, + Unit::UltimateTensileStrength, + Unit::YieldStrength, + Unit::YoungsModulus, + Unit::Stiffness, + Unit::StiffnessDensity, + Unit::Force, + Unit::Work, + Unit::Power, + Unit::Moment, + Unit::SpecificEnergy, + Unit::ThermalConductivity, + Unit::ThermalExpansionCoefficient, + Unit::VolumetricThermalExpansionCoefficient, + Unit::SpecificHeat, + Unit::ThermalTransferCoefficient, + Unit::HeatFlux, + Unit::DynamicViscosity, + Unit::KinematicViscosity, + Unit::VacuumPermittivity, + Unit::VolumeFlowRate, + Unit::DissipationRate, + Unit::InverseLength, + Unit::InverseArea, + Unit::InverseVolume, + }); + + std::array values = {0.01, 0.1, 1.0, 10.0, 100.0}; + + double factor {}; + std::string unitString; + + UnitsApi::setDecimals(16); + + UnitsApi::setSchema("Internal"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("MKS"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("Imperial"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_NEAR(q2.getValue(), value, 0.001); + } + } + + UnitsApi::setSchema("ImperialDecimal"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_NEAR(q2.getValue(), value, 0.001); + } + } + + UnitsApi::setSchema("Centimeter"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("MmMin"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("ImperialCivil"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_NEAR(q2.getValue(), value, 0.001); + } + } + + UnitsApi::setSchema("FEM"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("MeterDecimal"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } +} From bcd23743f4f9e0182b63c6ac565d5f1eb467bcbb Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Tue, 27 May 2025 12:08:05 +0200 Subject: [PATCH 03/11] Base: fix UnitsSchema::translate Schema translation previously returned factor 0 and unit string of input quantity if there was no translation match. Restore that behaviour as well as returned factor and unit string of imperial schemas. Fixes: 1155f0d75281 ("Base: simplify UnitsSchemas management") --- src/Base/UnitsSchema.cpp | 16 +++++++--------- src/Base/UnitsSchemasData.h | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/Base/UnitsSchema.cpp b/src/Base/UnitsSchema.cpp index 1fbc4e0169..3665ea8c2f 100644 --- a/src/Base/UnitsSchema.cpp +++ b/src/Base/UnitsSchema.cpp @@ -53,22 +53,20 @@ std::string UnitsSchema::translate(const Quantity& quant) const std::string UnitsSchema::translate(const Quantity& quant, double& factor, std::string& unitString) const { + // Use defaults without schema-level translation. + factor = 1.0; + unitString = quant.getUnit().getString(); + if (spec.translationSpecs.empty()) { - return toLocale(quant, 1.0, unitString); + return toLocale(quant, factor, unitString); } const auto unitName = quant.getUnit().getTypeString(); - - if (spec.translationSpecs.count(unitName) == 0) { - // no schema-level translation. Use defaults. - factor = 1.0; - unitString = quant.getUnit().getString(); - + if (!spec.translationSpecs.contains(unitName)) { return toLocale(quant, factor, unitString); } const auto value = quant.getValue(); - auto isSuitable = [&](const UnitTranslationSpec& row) { return row.threshold > value || row.threshold == 0; // zero indicates default }; @@ -81,7 +79,7 @@ UnitsSchema::translate(const Quantity& quant, double& factor, std::string& unitS } if (unitSpec->factor == 0) { - return UnitsSchemasData::runSpecial(unitSpec->unitString, value); + return UnitsSchemasData::runSpecial(unitSpec->unitString, value, factor, unitString); } factor = unitSpec->factor; diff --git a/src/Base/UnitsSchemasData.h b/src/Base/UnitsSchemasData.h index 8f937c1a50..45f25bdc98 100644 --- a/src/Base/UnitsSchemasData.h +++ b/src/Base/UnitsSchemasData.h @@ -733,17 +733,27 @@ inline std::string toDms(const double value) * Special functions caller */ -inline const std::map> specials // clang-format off +// clang-format off +inline const std::map> specials { { - { "toDMS" , [](const double val) { return toDms(val); }}, - { "toFractional" , [](const double val) { return toFractional(val); }} + { "toDMS" , [](const double val, double& factor, std::string& unitString) { + factor = 1.0; + unitString = "deg"; + return toDms(val); + }}, + { "toFractional" , [](const double val, double& factor, std::string& unitString) { + factor = 25.4; + unitString = "in"; + return toFractional(val); + }} } }; // clang-format on -inline std::string runSpecial(const std::string& name, const double value) +inline std::string +runSpecial(const std::string& name, const double value, double& factor, std::string& unitString) { - return specials.contains(name) ? specials.at(name)(value) : ""; + return specials.contains(name) ? specials.at(name)(value, factor, unitString) : ""; } From 8fe73e1f2c3561e86d3abc74ad469bd1c1dc8977 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Tue, 20 May 2025 11:24:20 +0200 Subject: [PATCH 04/11] Base: minor refactoring of UnitsSchema::toLocale --- src/Base/UnitsSchema.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Base/UnitsSchema.cpp b/src/Base/UnitsSchema.cpp index 3665ea8c2f..921d245dc5 100644 --- a/src/Base/UnitsSchema.cpp +++ b/src/Base/UnitsSchema.cpp @@ -94,20 +94,17 @@ UnitsSchema::toLocale(const Quantity& quant, const double factor, const std::str QLocale Lc; const QuantityFormat& format = quant.getFormat(); if (format.option != QuantityFormat::None) { - int opt = format.option; - Lc.setNumberOptions(static_cast(opt)); + Lc.setNumberOptions(static_cast(format.option)); } - std::string valueString = - Lc.toString((quant.getValue() / factor), format.toFormat(), format.precision).toStdString(); + auto valueString = + Lc.toString(quant.getValue() / factor, format.toFormat(), format.precision).toStdString(); - return fmt::format("{}{}{}", - valueString, - unitString.empty() || unitString == "°" || unitString == "″" - || unitString == "′" || unitString == "\"" || unitString == "'" - ? "" - : " ", - unitString); + auto notUnit = [](auto s) { + return s.empty() || s == "°" || s == "″" || s == "′" || s == "\"" || s == "'"; + }; + + return fmt::format("{}{}{}", valueString, notUnit(unitString) ? "" : " ", unitString); } bool UnitsSchema::isMultiUnitLength() const From ae80d51d5ddd1ec5755193572169aad0e97378e0 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Sun, 6 Apr 2025 21:56:12 +0200 Subject: [PATCH 05/11] Gui: do not create intermediate Quantity class in property editor --- src/Gui/propertyeditor/PropertyItem.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 7ebe9db4bd..1a180c6197 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -1786,15 +1786,11 @@ void PropertyVectorDistanceItem::setValue(const QVariant& variant) } const Base::Vector3d& value = variant.value(); - Base::Quantity x = Base::Quantity(value.x, Base::Unit::Length); - Base::Quantity y = Base::Quantity(value.y, Base::Unit::Length); - Base::Quantity z = Base::Quantity(value.z, Base::Unit::Length); - Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); std::string val = fmt::format("({}, {}, {})", - Base::UnitsApi::toNumber(x, format), - Base::UnitsApi::toNumber(y, format), - Base::UnitsApi::toNumber(z, format)); + Base::UnitsApi::toNumber(value.x, format), + Base::UnitsApi::toNumber(value.y, format), + Base::UnitsApi::toNumber(value.z, format)); setPropertyValue(val); } From d972e07e113a46a09e5fecfb305591db50216652 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Wed, 21 May 2025 10:03:43 +0200 Subject: [PATCH 06/11] Gui: use Units namespace in DlgUnitsCalculatorImp --- src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp | 61 +++++++++++------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp b/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp index 65947b6179..bf59585041 100644 --- a/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp +++ b/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp @@ -33,6 +33,9 @@ #include using namespace Gui::Dialog; +using Base::Quantity; +using Base::Unit; +using Base::UnitsApi; /* TRANSLATOR Gui::Dialog::DlgUnitsCalculator */ @@ -55,7 +58,7 @@ DlgUnitsCalculator::DlgUnitsCalculator(QWidget* parent, Qt::WindowFlags fl) auto addItem = [&, index {0}](const auto& item) mutable { ui->comboBoxScheme->addItem(QString::fromStdString(item), index++); }; - auto descriptions = Base::UnitsApi::getDescriptions(); + auto descriptions = UnitsApi::getDescriptions(); std::for_each(descriptions.begin(), descriptions.end(), addItem); // clang-format off @@ -65,7 +68,7 @@ DlgUnitsCalculator::DlgUnitsCalculator(QWidget* parent, Qt::WindowFlags fl) this, &DlgUnitsCalculator::onComboBoxSchemeActivated); connect(ui->spinBoxDecimals, qOverload(&QSpinBox::valueChanged), this, &DlgUnitsCalculator::onSpinBoxDecimalsValueChanged); - connect(ui->ValueInput, qOverload(&InputField::valueChanged), + connect(ui->ValueInput, qOverload(&InputField::valueChanged), this, &DlgUnitsCalculator::valueChanged); connect(ui->ValueInput, &InputField::returnPressed, this, &DlgUnitsCalculator::returnPressed); @@ -86,31 +89,27 @@ DlgUnitsCalculator::DlgUnitsCalculator(QWidget* parent, Qt::WindowFlags fl) ui->ValueInput->setText(QStringLiteral("1 cm")); ui->UnitInput->setText(QStringLiteral("in")); - units << Base::Unit::Acceleration << Base::Unit::AmountOfSubstance << Base::Unit::Angle - << Base::Unit::Area << Base::Unit::Density << Base::Unit::CurrentDensity - << Base::Unit::DissipationRate << Base::Unit::DynamicViscosity - << Base::Unit::ElectricalCapacitance << Base::Unit::ElectricalInductance - << Base::Unit::ElectricalConductance << Base::Unit::ElectricalResistance - << Base::Unit::ElectricalConductivity << Base::Unit::ElectricCharge - << Base::Unit::ElectricCurrent << Base::Unit::ElectricPotential << Base::Unit::Force - << Base::Unit::Frequency << Base::Unit::HeatFlux << Base::Unit::InverseArea - << Base::Unit::InverseLength << Base::Unit::InverseVolume - << Base::Unit::KinematicViscosity << Base::Unit::Length << Base::Unit::LuminousIntensity - << Base::Unit::Mass << Base::Unit::MagneticFieldStrength << Base::Unit::MagneticFlux - << Base::Unit::MagneticFluxDensity << Base::Unit::Magnetization << Base::Unit::Power - << Base::Unit::Pressure << Base::Unit::SpecificEnergy << Base::Unit::SpecificHeat - << Base::Unit::Stiffness << Base::Unit::Temperature << Base::Unit::ThermalConductivity - << Base::Unit::ThermalExpansionCoefficient << Base::Unit::ThermalTransferCoefficient - << Base::Unit::TimeSpan << Base::Unit::VacuumPermittivity << Base::Unit::Velocity - << Base::Unit::Volume << Base::Unit::VolumeFlowRate - << Base::Unit::VolumetricThermalExpansionCoefficient << Base::Unit::Work; - for (const Base::Unit& it : units) { + units << Unit::Acceleration << Unit::AmountOfSubstance << Unit::Angle << Unit::Area + << Unit::Density << Unit::CurrentDensity << Unit::DissipationRate + << Unit::DynamicViscosity << Unit::ElectricalCapacitance << Unit::ElectricalInductance + << Unit::ElectricalConductance << Unit::ElectricalResistance + << Unit::ElectricalConductivity << Unit::ElectricCharge << Unit::ElectricCurrent + << Unit::ElectricPotential << Unit::Force << Unit::Frequency << Unit::HeatFlux + << Unit::InverseArea << Unit::InverseLength << Unit::InverseVolume + << Unit::KinematicViscosity << Unit::Length << Unit::LuminousIntensity << Unit::Mass + << Unit::MagneticFieldStrength << Unit::MagneticFlux << Unit::MagneticFluxDensity + << Unit::Magnetization << Unit::Power << Unit::Pressure << Unit::SpecificEnergy + << Unit::SpecificHeat << Unit::Stiffness << Unit::Temperature << Unit::ThermalConductivity + << Unit::ThermalExpansionCoefficient << Unit::ThermalTransferCoefficient << Unit::TimeSpan + << Unit::VacuumPermittivity << Unit::Velocity << Unit::Volume << Unit::VolumeFlowRate + << Unit::VolumetricThermalExpansionCoefficient << Unit::Work; + for (const Unit& it : units) { ui->unitsBox->addItem(QString::fromStdString(it.getTypeString())); } ui->quantitySpinBox->setValue(1.0); ui->quantitySpinBox->setUnit(units.front()); - ui->spinBoxDecimals->setValue(Base::UnitsApi::getDecimals()); + ui->spinBoxDecimals->setValue(UnitsApi::getDecimals()); } /** Destroys the object and frees any allocated resources */ @@ -132,27 +131,27 @@ void DlgUnitsCalculator::textChanged(QString unit) valueChanged(actValue); } -void DlgUnitsCalculator::valueChanged(const Base::Quantity& quant) +void DlgUnitsCalculator::valueChanged(const Quantity& quant) { // first check the unit, if it is invalid, getTypeString() outputs an empty string - // explicitly check for "ee" like in "eeV" because this would trigger an exception in Base::Unit + // explicitly check for "ee" like in "eeV" because this would trigger an exception in Unit // since it expects then a scientific notation number like "1e3" if ((ui->UnitInput->text().mid(0, 2) == QStringLiteral("ee")) - || Base::Unit(ui->UnitInput->text().toStdString()).getTypeString().empty()) { + || Unit(ui->UnitInput->text().toStdString()).getTypeString().empty()) { ui->ValueOutput->setText( QStringLiteral("%1 %2").arg(tr("unknown unit:"), ui->UnitInput->text())); ui->pushButton_Copy->setEnabled(false); } else { // the unit is valid // we can only convert units of the same type, thus check - if (Base::Unit(ui->UnitInput->text().toStdString()).getTypeString() + if (Unit(ui->UnitInput->text().toStdString()).getTypeString() != quant.getUnit().getTypeString()) { ui->ValueOutput->setText(tr("unit mismatch")); ui->pushButton_Copy->setEnabled(false); } else { // the unit is valid and has the same type double convertValue = - Base::Quantity::parse("1" + ui->UnitInput->text().toStdString()).getValue(); + Quantity::parse("1" + ui->UnitInput->text().toStdString()).getValue(); // we got now e.g. for "1 in" the value '25.4' because 1 in = 25.4 mm // the result is now just quant / convertValue because the input is always in a base // unit (an input of "1 cm" will immediately be converted to "10 mm" by Gui::InputField @@ -164,7 +163,7 @@ void DlgUnitsCalculator::valueChanged(const Base::Quantity& quant) // "10 um" in "in", thus only if value > 0.005 because FC's default are 2 decimals QString val = QLocale().toString(value, 'g'); if (!val.contains(QChar::fromLatin1('e')) && (value > 0.005)) { - val = QLocale().toString(value, 'f', Base::UnitsApi::getDecimals()); + val = QLocale().toString(value, 'f', UnitsApi::getDecimals()); } // create the output string QString out = QStringLiteral("%1 %2").arg(val, ui->UnitInput->text()); @@ -201,13 +200,13 @@ void DlgUnitsCalculator::onUnitsBoxActivated(int index) { // SI units use [m], not [mm] for lengths // - Base::Quantity q = ui->quantitySpinBox->value(); + Quantity q = ui->quantitySpinBox->value(); int32_t old = q.getUnit().length(); double value = q.getValue(); - Base::Unit unit = units[index]; + Unit unit = units[index]; int32_t len = unit.length(); - ui->quantitySpinBox->setValue(Base::Quantity(value * std::pow(10.0, 3 * (len - old)), unit)); + ui->quantitySpinBox->setValue(Quantity(value * std::pow(10.0, 3 * (len - old)), unit)); } void DlgUnitsCalculator::onComboBoxSchemeActivated(int index) From f486b7c84b19fc2874db242fb8d1b66eb5aa6fcb Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Fri, 11 Apr 2025 01:53:09 +0200 Subject: [PATCH 07/11] App: Quantity: use predefined unit types --- src/App/FeatureTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/FeatureTest.cpp b/src/App/FeatureTest.cpp index e645193844..93bbcb95ab 100644 --- a/src/App/FeatureTest.cpp +++ b/src/App/FeatureTest.cpp @@ -145,7 +145,7 @@ FeatureTest::FeatureTest() ADD_PROPERTY(QuantityLength, (1.0)); QuantityLength.setUnit(Base::Unit::Length); ADD_PROPERTY(QuantityOther, (5.0)); - QuantityOther.setUnit(Base::Unit(-3, 1)); + QuantityOther.setUnit(Base::Unit::Density); // clang-format on } From 913c30429cabe1bc6d7788a96fa4975ca2d4ff91 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Thu, 10 Apr 2025 17:43:41 +0200 Subject: [PATCH 08/11] Base: Quantity: use isDimensionless whenever feasible Quantity is often queried for Unit just to see if it has a dimension. Ask Quantity directly using isDimensionless() method and modify that method not to care about Quantity value validity; no user was ever asking for value validity. --- src/App/Expression.cpp | 2 +- src/App/PropertyUnits.cpp | 12 ++++---- src/Base/Quantity.cpp | 10 ++----- src/Base/Quantity.h | 4 +-- src/Base/QuantityPyImp.cpp | 38 +++++++++--------------- src/Gui/Dialogs/DlgExpressionInput.cpp | 4 +-- src/Gui/InputField.cpp | 6 ++-- src/Gui/QuantitySpinBox.cpp | 2 +- src/Mod/Sketcher/Gui/EditDatumDialog.cpp | 4 +-- tests/src/Base/Quantity.cpp | 7 ----- 10 files changed, 31 insertions(+), 58 deletions(-) diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index 6e9db2bc83..9275e678b1 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -488,7 +488,7 @@ static inline Quantity pyToQuantity(const Py::Object &pyobj, } Py::Object pyFromQuantity(const Quantity &quantity) { - if(!quantity.getUnit().isEmpty()) + if (!quantity.isDimensionless()) return Py::asObject(new QuantityPy(new Quantity(quantity))); double v = quantity.getValue(); long l; diff --git a/src/App/PropertyUnits.cpp b/src/App/PropertyUnits.cpp index 300c3ca786..ca38aed84a 100644 --- a/src/App/PropertyUnits.cpp +++ b/src/App/PropertyUnits.cpp @@ -105,13 +105,12 @@ void PropertyQuantity::setPyObject(PyObject* value) else { Base::Quantity quant = createQuantityFromPy(value); - Unit unit = quant.getUnit(); - if (unit.isEmpty()) { + if (quant.isDimensionless()) { PropertyFloat::setValue(quant.getValue()); return; } - if (unit != _Unit) { + if (_Unit != quant.getUnit()) { throw Base::UnitsMismatchError("Not matching Unit!"); } @@ -123,7 +122,7 @@ void PropertyQuantity::setPathValue(const ObjectIdentifier& /*path*/, const boos { auto q = App::anyToQuantity(value); aboutToSetValue(); - if (!q.getUnit().isEmpty()) { + if (!q.isDimensionless()) { _Unit = q.getUnit(); } _dValue = q.getValue(); @@ -187,7 +186,6 @@ void PropertyQuantityConstraint::setPyObject(PyObject* value) { Base::Quantity quant = createQuantityFromPy(value); - Unit unit = quant.getUnit(); double temp = quant.getValue(); if (_ConstStruct) { if (temp > _ConstStruct->UpperBound) { @@ -199,12 +197,12 @@ void PropertyQuantityConstraint::setPyObject(PyObject* value) } quant.setValue(temp); - if (unit.isEmpty()) { + if (quant.isDimensionless()) { PropertyFloat::setValue(quant.getValue()); // clazy:exclude=skipped-base-method return; } - if (unit != _Unit) { + if (_Unit != quant.getUnit()) { throw Base::UnitsMismatchError("Not matching Unit!"); } diff --git a/src/Base/Quantity.cpp b/src/Base/Quantity.cpp index efa8c37a3c..babbe4a4c2 100644 --- a/src/Base/Quantity.cpp +++ b/src/Base/Quantity.cpp @@ -183,7 +183,7 @@ Quantity Quantity::operator/(double factor) const Quantity Quantity::pow(const Quantity& other) const { - if (!other.myUnit.isEmpty()) { + if (!other.isDimensionless()) { throw Base::UnitsMismatchError("Quantity::pow(): exponent must not have a unit"); } @@ -273,7 +273,7 @@ std::string Quantity::getSafeUserString() const /// true if it has a number without a unit bool Quantity::isDimensionless() const { - return isValid() && myUnit.isEmpty(); + return myUnit.isEmpty(); } /// true if it has a specific unit or no dimension. @@ -282,12 +282,6 @@ bool Quantity::isDimensionlessOrUnit(const Unit& unit) const return isDimensionless() || myUnit == unit; } -// true if it has a number and a valid unit -bool Quantity::isQuantity() const -{ - return isValid() && !myUnit.isEmpty(); -} - // true if it has a number with or without a unit bool Quantity::isValid() const { diff --git a/src/Base/Quantity.h b/src/Base/Quantity.h index ed0f947ac9..657f3af1b4 100644 --- a/src/Base/Quantity.h +++ b/src/Base/Quantity.h @@ -191,12 +191,10 @@ public: double getValueAs(const Quantity&) const; - /// true if it has a number without a unit + /// true if it has no unit bool isDimensionless() const; /// true if it has a specific unit or no dimension. bool isDimensionlessOrUnit(const Unit& unit) const; - /// true if it has a number and a valid unit - bool isQuantity() const; /// true if it has a number with or without a unit bool isValid() const; /// sets the quantity invalid diff --git a/src/Base/QuantityPyImp.cpp b/src/Base/QuantityPyImp.cpp index d832945b5e..1fea8807c6 100644 --- a/src/Base/QuantityPyImp.cpp +++ b/src/Base/QuantityPyImp.cpp @@ -43,19 +43,15 @@ using Base::Quantity; // returns a string which represents the object e.g. when printed in python std::string QuantityPy::representation() const { - std::stringstream ret; - - double val = getQuantityPtr()->getValue(); - Unit unit = getQuantityPtr()->getUnit(); - + std::stringstream ss; // Use Python's implementation to repr() a float - Py::Float flt(val); - ret << static_cast(flt.repr()); - if (!unit.isEmpty()) { - ret << " " << unit.getString(); + Py::Float flt(getQuantityPtr()->getValue()); + ss << static_cast(flt.repr()); + if (!getQuantityPtr()->isDimensionless()) { + ss << " " << getQuantityPtr()->getUnit().getString(); } - return ret.str(); + return ss.str(); } PyObject* QuantityPy::toStr(PyObject* args) const @@ -66,17 +62,16 @@ PyObject* QuantityPy::toStr(PyObject* args) const } double val = getQuantityPtr()->getValue(); - Unit unit = getQuantityPtr()->getUnit(); - std::stringstream ret; - ret.precision(prec); - ret.setf(std::ios::fixed, std::ios::floatfield); - ret << val; - if (!unit.isEmpty()) { - ret << " " << unit.getString(); + std::stringstream ss; + ss.precision(prec); + ss.setf(std::ios::fixed, std::ios::floatfield); + ss << val; + if (!getQuantityPtr()->isDimensionless()) { + ss << " " << getQuantityPtr()->getUnit().getString(); } - return Py_BuildValue("s", ret.str().c_str()); + return Py_BuildValue("s", ss.str().c_str()); } PyObject* QuantityPy::PyMake(PyTypeObject* /*unused*/, PyObject* /*unused*/, PyObject* /*unused*/) @@ -279,11 +274,6 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const } const auto qpUnit = qPtr->getUnit(); - if (qpUnit.isEmpty()) { - err("QuantityPtr returned empty unit"); - return false; - } - if (const auto qUnit = quant.getUnit(); qUnit != qpUnit) { err("Unit mismatch (`" + qUnit.getString() + "` != `" + qpUnit.getString() + "`)"); return false; @@ -301,7 +291,7 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const } const auto quant = optQuant.value(); - if (quant.isQuantity()) { + if (!quant.isDimensionless()) { if (!checkQuant(quant)) { return nullptr; } diff --git a/src/Gui/Dialogs/DlgExpressionInput.cpp b/src/Gui/Dialogs/DlgExpressionInput.cpp index 336bbbd2e5..b8ee7c36b1 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.cpp +++ b/src/Gui/Dialogs/DlgExpressionInput.cpp @@ -295,13 +295,13 @@ void DlgExpressionInput::checkExpression(const QString& text) auto msg = value.getUserString(); if (!impliedUnit.isEmpty()) { - if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit) + if (!value.isDimensionless() && value.getUnit() != impliedUnit) throw Base::UnitsMismatchError("Unit mismatch between result and required unit"); value.setUnit(impliedUnit); } - else if (!value.getUnit().isEmpty()) { + else if (!value.isDimensionless()) { msg += " (Warning: unit discarded)"; QPalette p(ui->msg->palette()); diff --git a/src/Gui/InputField.cpp b/src/Gui/InputField.cpp index e203e0bc69..d6ca20d1a3 100644 --- a/src/Gui/InputField.cpp +++ b/src/Gui/InputField.cpp @@ -278,11 +278,11 @@ void InputField::newInput(const QString & text) return; } - if (res.getUnit().isEmpty()) + if (res.isDimensionless()) res.setUnit(this->actUnit); // check if unit fits! - if(!actUnit.isEmpty() && !res.getUnit().isEmpty() && actUnit != res.getUnit()){ + if (!actUnit.isEmpty() && !res.isDimensionless() && actUnit != res.getUnit()){ if (iconLabel->isHidden()) { iconLabel->setVisible(true); } @@ -644,7 +644,7 @@ void InputField::focusInEvent(QFocusEvent *event) void InputField::focusOutEvent(QFocusEvent *event) { try { - if (Quantity::parse(this->text().toStdString()).getUnit().isEmpty()) { + if (Quantity::parse(this->text().toStdString()).isDimensionless()) { // if user didn't enter a unit, we virtually compensate // the multiplication factor induced by user unit system double factor; diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index 59f88073bc..4631b45c75 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -224,7 +224,7 @@ public: ok = parseString(copy, res, value, path); // If result does not have unit: add default unit - if (res.getUnit().isEmpty()){ + if (res.isDimensionless()) { res.setUnit(unit); } diff --git a/src/Mod/Sketcher/Gui/EditDatumDialog.cpp b/src/Mod/Sketcher/Gui/EditDatumDialog.cpp index e04bd45b48..fd8337da31 100644 --- a/src/Mod/Sketcher/Gui/EditDatumDialog.cpp +++ b/src/Mod/Sketcher/Gui/EditDatumDialog.cpp @@ -201,8 +201,8 @@ int EditDatumDialog::exec(bool atCursor) void EditDatumDialog::accepted() { Base::Quantity newQuant = ui_ins_datum->labelEdit->value(); - if (newQuant.isQuantity() || (Constr->Type == Sketcher::SnellsLaw && newQuant.isDimensionless()) - || (Constr->Type == Sketcher::Weight && newQuant.isDimensionless())) { + if (Constr->Type == Sketcher::SnellsLaw || Constr->Type == Sketcher::Weight + || !newQuant.isDimensionless()) { // save the value for the history ui_ins_datum->labelEdit->pushToHistory(); diff --git a/tests/src/Base/Quantity.cpp b/tests/src/Base/Quantity.cpp index 7e555d7e71..d333236271 100644 --- a/tests/src/Base/Quantity.cpp +++ b/tests/src/Base/Quantity.cpp @@ -27,13 +27,6 @@ TEST(BaseQuantity, TestParse) EXPECT_THROW(auto rew [[maybe_unused]] = Quantity::parse("1,234,500.12 kg"), ParserError); } -TEST(BaseQuantity, TestDim) -{ - const Quantity q1 {0, Unit::Area}; - - EXPECT_EQ(q1.isQuantity(), true); -} - TEST(BaseQuantity, TestNoDim) { const Quantity q1 {}; From 1c955c9347841e0b285b1a523beb298606aceb36 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Fri, 11 Apr 2025 02:12:42 +0200 Subject: [PATCH 09/11] Fem: get unit via quantity Unit is interally using Quantity to parse string input, so use it explicitely as string constructor for Unit is going to be dropped. --- src/Mod/Fem/App/FemPostPipeline.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/App/FemPostPipeline.cpp b/src/Mod/Fem/App/FemPostPipeline.cpp index 2689b66278..70697e5bfe 100644 --- a/src/Mod/Fem/App/FemPostPipeline.cpp +++ b/src/Mod/Fem/App/FemPostPipeline.cpp @@ -611,12 +611,11 @@ Base::Unit FemPostPipeline::getFrameUnit() vtkAbstractArray* TimeInfo = multiblock->GetFieldData()->GetAbstractArray("TimeInfo"); if (!TimeInfo->IsA("vtkStringArray") || TimeInfo->GetNumberOfTuples() < 2) { - // units cannot be undefined, so use time return Base::Unit::TimeSpan; } - - return Base::Unit(vtkStringArray::SafeDownCast(TimeInfo)->GetValue(1)); + auto qty = Base::Quantity(0, vtkStringArray::SafeDownCast(TimeInfo)->GetValue(1)); + return qty.getUnit(); } std::vector FemPostPipeline::getFrameValues() From f8d2789a43f99d1c58adc479f308a49f1bc9ba82 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Thu, 10 Apr 2025 19:15:02 +0200 Subject: [PATCH 10/11] Base: Units: introduce unit one Dimensionless quantities have all exponents equal to zero. Such quantities are simply numbers. The associated unit is the unit one, symbol 1, although this is rarely explicitly written. See chapter 2.3.3 Dimensions of quantities, The International System of Units, 9th edition. --- src/App/PropertyGeo.cpp | 4 ++-- src/Base/Quantity.cpp | 4 ++-- src/Base/Unit.cpp | 14 ++++++++------ src/Base/Unit.h | 7 ++----- src/Gui/Dialogs/DlgExpressionInput.cpp | 2 +- src/Gui/InputField.cpp | 2 +- src/Mod/Spreadsheet/App/Cell.cpp | 4 ++-- src/Mod/Spreadsheet/App/Sheet.cpp | 2 +- src/Mod/Spreadsheet/Gui/SheetModel.cpp | 2 +- tests/src/Base/Unit.cpp | 2 +- 10 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/App/PropertyGeo.cpp b/src/App/PropertyGeo.cpp index c4a51c4419..ad857a41d0 100644 --- a/src/App/PropertyGeo.cpp +++ b/src/App/PropertyGeo.cpp @@ -194,7 +194,7 @@ void PropertyVector::getPaths(std::vector& paths) const const boost::any PropertyVector::getPathValue(const ObjectIdentifier& path) const { Base::Unit unit = getUnit(); - if (!unit.isEmpty()) { + if (unit != Unit::One) { std::string p = path.getSubPathStr(); if (p == ".x" || p == ".y" || p == ".z") { // Convert double to quantity @@ -207,7 +207,7 @@ const boost::any PropertyVector::getPathValue(const ObjectIdentifier& path) cons bool PropertyVector::getPyPathValue(const ObjectIdentifier& path, Py::Object& res) const { Base::Unit unit = getUnit(); - if (unit.isEmpty()) { + if (unit == Unit::One) { return false; } diff --git a/src/Base/Quantity.cpp b/src/Base/Quantity.cpp index babbe4a4c2..a9c6fcb467 100644 --- a/src/Base/Quantity.cpp +++ b/src/Base/Quantity.cpp @@ -270,10 +270,10 @@ std::string Quantity::getSafeUserString() const return Tools::escapeQuotesFromString(userStr); } -/// true if it has a number without a unit +/// true if unit equals to 1, therefore quantity has no dimension bool Quantity::isDimensionless() const { - return myUnit.isEmpty(); + return myUnit == Unit::One; } /// true if it has a specific unit or no dimension. diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index baa782972d..8b8570879a 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -281,11 +281,6 @@ int Unit::angle() const return sig.Angle; } -bool Unit::isEmpty() const -{ - return Val == 0; -} - int Unit::operator [](int index) const { UnitSignature sig; @@ -318,6 +313,11 @@ bool Unit::operator ==(const Unit& that) const return Val == that.Val; } +bool Unit::operator !=(const Unit& that) const +{ + return Val != that.Val; +} + Unit Unit::operator *(const Unit &right) const { Unit result; @@ -360,7 +360,7 @@ Unit Unit::operator /(const Unit &right) const std::string Unit::getString() const { - if (isEmpty()) { + if (Val == 0) { return {}; } @@ -658,6 +658,8 @@ std::string Unit::getTypeString() const return spec->second; } +const Unit Unit::One (0, 0, 0, 0, 0, 0, 0, 0); + // SI base units const Unit Unit::AmountOfSubstance (0, 0, 0, 0, 0, 1); const Unit Unit::ElectricCurrent (0, 0, 0, 1); diff --git a/src/Base/Unit.h b/src/Base/Unit.h index 77b2273b3d..f5e6197bf2 100644 --- a/src/Base/Unit.h +++ b/src/Base/Unit.h @@ -61,10 +61,7 @@ public: Unit operator*(const Unit&) const; Unit operator/(const Unit&) const; bool operator==(const Unit&) const; - bool operator!=(const Unit& that) const - { - return !(*this == that); - } + bool operator!=(const Unit& that) const; Unit& operator=(const Unit&) = default; Unit& operator=(Unit&&) = default; Unit pow(double exp) const; @@ -79,7 +76,6 @@ public: int amountOfSubstance() const; int luminousIntensity() const; int angle() const; - bool isEmpty() const; std::string getString() const; /// get the type as an string such as "Area", "Length" or "Pressure". @@ -87,6 +83,7 @@ public: /** Predefined Unit types. */ //@{ + static const Unit One; /// Length unit static const Unit Length; /// Mass unit diff --git a/src/Gui/Dialogs/DlgExpressionInput.cpp b/src/Gui/Dialogs/DlgExpressionInput.cpp index b8ee7c36b1..e626ab7cb3 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.cpp +++ b/src/Gui/Dialogs/DlgExpressionInput.cpp @@ -294,7 +294,7 @@ void DlgExpressionInput::checkExpression(const QString& text) } auto msg = value.getUserString(); - if (!impliedUnit.isEmpty()) { + if (impliedUnit != Base::Unit::One) { if (!value.isDimensionless() && value.getUnit() != impliedUnit) throw Base::UnitsMismatchError("Unit mismatch between result and required unit"); diff --git a/src/Gui/InputField.cpp b/src/Gui/InputField.cpp index d6ca20d1a3..ed418866a3 100644 --- a/src/Gui/InputField.cpp +++ b/src/Gui/InputField.cpp @@ -282,7 +282,7 @@ void InputField::newInput(const QString & text) res.setUnit(this->actUnit); // check if unit fits! - if (!actUnit.isEmpty() && !res.isDimensionless() && actUnit != res.getUnit()){ + if (actUnit != Unit::One && !res.isDimensionless() && actUnit != res.getUnit()){ if (iconLabel->isHidden()) { iconLabel->setVisible(true); } diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 324ff7bb61..9668a52d14 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -607,7 +607,7 @@ void Cell::setComputedUnit(const Base::Unit& unit) PropertySheet::AtomicPropertyChange signaller(*owner); computedUnit = unit; - setUsed(COMPUTED_UNIT_SET, !computedUnit.isEmpty()); + setUsed(COMPUTED_UNIT_SET, computedUnit != Unit::One); setDirty(); signaller.tryInvoke(); @@ -1110,7 +1110,7 @@ std::string Cell::getFormattedQuantity() const Base::Unit& computedUnit = floatProp->getUnit(); qFormatted = QLocale().toString(rawVal, 'f', Base::UnitsApi::getDecimals()); if (hasDisplayUnit) { - if (computedUnit.isEmpty() || computedUnit == du.unit) { + if (computedUnit == Unit::One || computedUnit == du.unit) { QString number = QLocale().toString(rawVal / duScale, 'f', Base::UnitsApi::getDecimals()); qFormatted = number + QString::fromStdString(" " + displayUnit.stringRep); diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index e2253da0cd..eb4fea54c5 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -802,7 +802,7 @@ void Sheet::updateProperty(CellAddress key) Base::PyGILStateLocker lock; setObjectProperty(key, constant->getPyValue()); } - else if (!number->getUnit().isEmpty()) { + else if (number->getUnit() != Unit::One) { setQuantityProperty(key, number->getValue(), number->getUnit()); } else if (number->isInteger(&l)) { diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index 9f097aac73..befa29c199 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -384,7 +384,7 @@ QVariant SheetModel::data(const QModelIndex& index, int role) const // Display locale specific decimal separator (#0003875,#0003876) if (cell->getDisplayUnit(displayUnit)) { - if (computedUnit.isEmpty() || computedUnit == displayUnit.unit) { + if (computedUnit == Base::Unit::One || computedUnit == displayUnit.unit) { QString number = QLocale().toString(floatProp->getValue() / displayUnit.scaler, 'f', diff --git a/tests/src/Base/Unit.cpp b/tests/src/Base/Unit.cpp index fa73d91106..b34386d935 100644 --- a/tests/src/Base/Unit.cpp +++ b/tests/src/Base/Unit.cpp @@ -121,7 +121,7 @@ TEST(Unit, TestPowNoDim) { Base::Unit unit {}; EXPECT_EQ(unit.pow(2), Base::Unit {0}); - EXPECT_EQ(unit.isEmpty(), true); + EXPECT_EQ(unit == Base::Unit::One, true); } TEST(Unit, TestPowEQ1) From 847e2f5c85fc184b0cd6d520e2ae9cb05ca3d189 Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Wed, 1 Jan 2025 18:17:00 +1000 Subject: [PATCH 11/11] Base: Units: refactor Refactor Units making it constexpr, immutable, with repetition reduced. Separate data from code. Constexpr constructed units allow constructing predefined Quantities from predefined unit types. --- src/Base/Quantity.cpp | 240 +++--- src/Base/QuantityPyImp.cpp | 53 +- src/Base/Unit.cpp | 998 +++++++--------------- src/Base/Unit.h | 268 +++--- src/Base/UnitPyImp.cpp | 56 +- src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp | 14 +- tests/src/App/ExpressionParser.cpp | 10 +- tests/src/Base/Unit.cpp | 245 +++--- 8 files changed, 729 insertions(+), 1155 deletions(-) diff --git a/src/Base/Quantity.cpp b/src/Base/Quantity.cpp index a9c6fcb467..4d10a79dc0 100644 --- a/src/Base/Quantity.cpp +++ b/src/Base/Quantity.cpp @@ -294,162 +294,150 @@ void Quantity::setInvalid() } // === Predefined types ===================================================== +// clang-format off +const Quantity Quantity::NanoMetre ( 1.0e-6 , Unit::Length ); +const Quantity Quantity::MicroMetre ( 1.0e-3 , Unit::Length ); +const Quantity Quantity::MilliMetre ( 1.0 , Unit::Length ); +const Quantity Quantity::CentiMetre ( 10.0 , Unit::Length ); +const Quantity Quantity::DeciMetre ( 100.0 , Unit::Length ); +const Quantity Quantity::Metre ( 1.0e3 , Unit::Length ); +const Quantity Quantity::KiloMetre ( 1.0e6 , Unit::Length ); -const Quantity Quantity::NanoMetre(1.0e-6, Unit(1)); -const Quantity Quantity::MicroMetre(1.0e-3, Unit(1)); -const Quantity Quantity::MilliMetre(1.0, Unit(1)); -const Quantity Quantity::CentiMetre(10.0, Unit(1)); -const Quantity Quantity::DeciMetre(100.0, Unit(1)); -const Quantity Quantity::Metre(1.0e3, Unit(1)); -const Quantity Quantity::KiloMetre(1.0e6, Unit(1)); +const Quantity Quantity::MilliLiter ( 1000.0 , Unit::Volume ); +const Quantity Quantity::Liter ( 1.0e6 , Unit::Volume ); -const Quantity Quantity::MilliLiter(1000.0, Unit(3)); -const Quantity Quantity::Liter(1.0e6, Unit(3)); +const Quantity Quantity::Hertz ( 1.0 , Unit::Frequency ); +const Quantity Quantity::KiloHertz ( 1.0e3 , Unit::Frequency ); +const Quantity Quantity::MegaHertz ( 1.0e6 , Unit::Frequency ); +const Quantity Quantity::GigaHertz ( 1.0e9 , Unit::Frequency ); +const Quantity Quantity::TeraHertz ( 1.0e12 , Unit::Frequency ); -const Quantity Quantity::Hertz(1.0, Unit(0, 0, -1)); -const Quantity Quantity::KiloHertz(1.0e3, Unit(0, 0, -1)); -const Quantity Quantity::MegaHertz(1.0e6, Unit(0, 0, -1)); -const Quantity Quantity::GigaHertz(1.0e9, Unit(0, 0, -1)); -const Quantity Quantity::TeraHertz(1.0e12, Unit(0, 0, -1)); +const Quantity Quantity::MicroGram ( 1.0e-9 , Unit::Mass ); +const Quantity Quantity::MilliGram ( 1.0e-6 , Unit::Mass ); +const Quantity Quantity::Gram ( 1.0e-3 , Unit::Mass ); +const Quantity Quantity::KiloGram ( 1.0 , Unit::Mass ); +const Quantity Quantity::Ton ( 1.0e3 , Unit::Mass ); -const Quantity Quantity::MicroGram(1.0e-9, Unit(0, 1)); -const Quantity Quantity::MilliGram(1.0e-6, Unit(0, 1)); -const Quantity Quantity::Gram(1.0e-3, Unit(0, 1)); -const Quantity Quantity::KiloGram(1.0, Unit(0, 1)); -const Quantity Quantity::Ton(1.0e3, Unit(0, 1)); +const Quantity Quantity::Second ( 1.0 , Unit::TimeSpan ); +const Quantity Quantity::Minute ( 60.0 , Unit::TimeSpan ); +const Quantity Quantity::Hour ( 3600.0 , Unit::TimeSpan ); -const Quantity Quantity::Second(1.0, Unit(0, 0, 1)); -const Quantity Quantity::Minute(60.0, Unit(0, 0, 1)); -const Quantity Quantity::Hour(3600.0, Unit(0, 0, 1)); +const Quantity Quantity::Ampere ( 1.0 , Unit::ElectricCurrent ); +const Quantity Quantity::MilliAmpere ( 0.001 , Unit::ElectricCurrent ); +const Quantity Quantity::KiloAmpere ( 1000.0 , Unit::ElectricCurrent ); +const Quantity Quantity::MegaAmpere ( 1.0e6 , Unit::ElectricCurrent ); -const Quantity Quantity::Ampere(1.0, Unit(0, 0, 0, 1)); -const Quantity Quantity::MilliAmpere(0.001, Unit(0, 0, 0, 1)); -const Quantity Quantity::KiloAmpere(1000.0, Unit(0, 0, 0, 1)); -const Quantity Quantity::MegaAmpere(1.0e6, Unit(0, 0, 0, 1)); +const Quantity Quantity::Kelvin ( 1.0 , Unit::Temperature ); +const Quantity Quantity::MilliKelvin ( 0.001 , Unit::Temperature ); +const Quantity Quantity::MicroKelvin ( 0.000001 , Unit::Temperature ); -const Quantity Quantity::Kelvin(1.0, Unit(0, 0, 0, 0, 1)); -const Quantity Quantity::MilliKelvin(0.001, Unit(0, 0, 0, 0, 1)); -const Quantity Quantity::MicroKelvin(0.000001, Unit(0, 0, 0, 0, 1)); +const Quantity Quantity::MilliMole ( 0.001 , Unit::AmountOfSubstance ); +const Quantity Quantity::Mole ( 1.0 , Unit::AmountOfSubstance ); -const Quantity Quantity::MilliMole(0.001, Unit(0, 0, 0, 0, 0, 1)); -const Quantity Quantity::Mole(1.0, Unit(0, 0, 0, 0, 0, 1)); +const Quantity Quantity::Candela ( 1.0 , Unit::LuminousIntensity ); -const Quantity Quantity::Candela(1.0, Unit(0, 0, 0, 0, 0, 0, 1)); +const Quantity Quantity::Inch ( 25.4 , Unit::Length ); +const Quantity Quantity::Foot ( 304.8 , Unit::Length ); +const Quantity Quantity::Thou ( 0.0254 , Unit::Length ); +const Quantity Quantity::Yard ( 914.4 , Unit::Length ); +const Quantity Quantity::Mile ( 1609344.0 , Unit::Length ); -const Quantity Quantity::Inch(25.4, Unit(1)); -const Quantity Quantity::Foot(304.8, Unit(1)); -const Quantity Quantity::Thou(0.0254, Unit(1)); -const Quantity Quantity::Yard(914.4, Unit(1)); -const Quantity Quantity::Mile(1609344.0, Unit(1)); +const Quantity Quantity::MilePerHour ( 447.04 , Unit::Velocity ); +const Quantity Quantity::SquareFoot ( 92903.04 , Unit::Area ); +const Quantity Quantity::CubicFoot ( 28316846.592 , Unit::Volume ); -const Quantity Quantity::MilePerHour(447.04, Unit(1, 0, -1)); -const Quantity Quantity::SquareFoot(92903.04, Unit(2)); -const Quantity Quantity::CubicFoot(28316846.592, Unit(3)); +const Quantity Quantity::Pound ( 0.45359237 , Unit::Mass ); +const Quantity Quantity::Ounce ( 0.0283495231 , Unit::Mass ); +const Quantity Quantity::Stone ( 6.35029318 , Unit::Mass ); +const Quantity Quantity::Hundredweights ( 50.80234544 , Unit::Mass ); -const Quantity Quantity::Pound(0.45359237, Unit(0, 1)); -const Quantity Quantity::Ounce(0.0283495231, Unit(0, 1)); -const Quantity Quantity::Stone(6.35029318, Unit(0, 1)); -const Quantity Quantity::Hundredweights(50.80234544, Unit(0, 1)); +const Quantity Quantity::PoundForce ( 4448.22 , Unit::Force ); // lbf are ~= 4.44822 Newton -const Quantity Quantity::PoundForce(4448.22, Unit(1, 1, -2)); // lbf are ~= 4.44822 Newton +const Quantity Quantity::Newton ( 1000.0 , Unit::Force ); // Newton (kg*m/s^2) +const Quantity Quantity::MilliNewton ( 1.0 , Unit::Force ); +const Quantity Quantity::KiloNewton ( 1e+6 , Unit::Force ); +const Quantity Quantity::MegaNewton ( 1e+9 , Unit::Force ); -const Quantity Quantity::Newton(1000.0, Unit(1, 1, -2)); // Newton (kg*m/s^2) -const Quantity Quantity::MilliNewton(1.0, Unit(1, 1, -2)); -const Quantity Quantity::KiloNewton(1e+6, Unit(1, 1, -2)); -const Quantity Quantity::MegaNewton(1e+9, Unit(1, 1, -2)); +const Quantity Quantity::NewtonPerMeter ( 1.00 , Unit::Stiffness ); // Newton per meter (N/m or kg/s^2) +const Quantity Quantity::MilliNewtonPerMeter ( 1e-3 , Unit::Stiffness ); +const Quantity Quantity::KiloNewtonPerMeter ( 1e3 , Unit::Stiffness ); +const Quantity Quantity::MegaNewtonPerMeter ( 1e6 , Unit::Stiffness ); -const Quantity Quantity::NewtonPerMeter(1.00, Unit(0, 1, -2)); // Newton per meter (N/m or kg/s^2) -const Quantity Quantity::MilliNewtonPerMeter(1e-3, Unit(0, 1, -2)); -const Quantity Quantity::KiloNewtonPerMeter(1e3, Unit(0, 1, -2)); -const Quantity Quantity::MegaNewtonPerMeter(1e6, Unit(0, 1, -2)); +const Quantity Quantity::Pascal ( 0.001 , Unit::CompressiveStrength ); // Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::KiloPascal ( 1.00 , Unit::CompressiveStrength ); +const Quantity Quantity::MegaPascal ( 1000.0 , Unit::CompressiveStrength ); +const Quantity Quantity::GigaPascal ( 1e+6 , Unit::CompressiveStrength ); -const Quantity Quantity::Pascal(0.001, Unit(-1, 1, -2)); // Pascal (kg/m/s^2 or N/m^2) -const Quantity Quantity::KiloPascal(1.00, Unit(-1, 1, -2)); -const Quantity Quantity::MegaPascal(1000.0, Unit(-1, 1, -2)); -const Quantity Quantity::GigaPascal(1e+6, Unit(-1, 1, -2)); +const Quantity Quantity::MilliBar ( 0.1 , Unit::CompressiveStrength ); +const Quantity Quantity::Bar ( 100.0 , Unit::CompressiveStrength ); // 1 bar = 100 kPa -const Quantity Quantity::MilliBar(0.1, Unit(-1, 1, -2)); -const Quantity Quantity::Bar(100.0, Unit(-1, 1, -2)); // 1 bar = 100 kPa +const Quantity Quantity::Torr ( 101.325 / 760.0 , Unit::CompressiveStrength ); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::mTorr ( 0.101325 / 760.0 , Unit::CompressiveStrength ); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::yTorr ( 0.000101325 / 760.0 , Unit::CompressiveStrength ); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) -const Quantity - Quantity::Torr(101.325 / 760.0, - Unit(-1, 1, -2)); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) -const Quantity - Quantity::mTorr(0.101325 / 760.0, - Unit(-1, 1, -2)); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) -const Quantity - Quantity::yTorr(0.000101325 / 760.0, - Unit(-1, 1, -2)); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::PSI ( 6.894744825494 , Unit::CompressiveStrength ); // pounds/in^2 +const Quantity Quantity::KSI ( 6894.744825494 , Unit::CompressiveStrength ); // 1000 x pounds/in^2 +const Quantity Quantity::MPSI ( 6894744.825494 , Unit::CompressiveStrength ); // 1000 ksi -const Quantity Quantity::PSI(6.894744825494, Unit(-1, 1, -2)); // pounds/in^2 -const Quantity Quantity::KSI(6894.744825494, Unit(-1, 1, -2)); // 1000 x pounds/in^2 -const Quantity Quantity::MPSI(6894744.825494, Unit(-1, 1, -2)); // 1000 ksi +const Quantity Quantity::Watt ( 1e+6 , Unit::Power ); // Watt (kg*m^2/s^3) +const Quantity Quantity::MilliWatt ( 1e+3 , Unit::Power ); +const Quantity Quantity::KiloWatt ( 1e+9 , Unit::Power ); +const Quantity Quantity::VoltAmpere ( 1e+6 , Unit::Power ); // VoltAmpere (kg*m^2/s^3) -const Quantity Quantity::Watt(1e+6, Unit(2, 1, -3)); // Watt (kg*m^2/s^3) -const Quantity Quantity::MilliWatt(1e+3, Unit(2, 1, -3)); -const Quantity Quantity::KiloWatt(1e+9, Unit(2, 1, -3)); -const Quantity Quantity::VoltAmpere(1e+6, Unit(2, 1, -3)); // VoltAmpere (kg*m^2/s^3) +const Quantity Quantity::Volt ( 1e+6 , Unit::ElectricPotential ); // Volt (kg*m^2/A/s^3) +const Quantity Quantity::MilliVolt ( 1e+3 , Unit::ElectricPotential ); +const Quantity Quantity::KiloVolt ( 1e+9 , Unit::ElectricPotential ); -const Quantity Quantity::Volt(1e+6, Unit(2, 1, -3, -1)); // Volt (kg*m^2/A/s^3) -const Quantity Quantity::MilliVolt(1e+3, Unit(2, 1, -3, -1)); -const Quantity Quantity::KiloVolt(1e+9, Unit(2, 1, -3, -1)); +const Quantity Quantity::MegaSiemens ( 1.0 , Unit::ElectricalConductance ); +const Quantity Quantity::KiloSiemens ( 1e-3 , Unit::ElectricalConductance ); +const Quantity Quantity::Siemens ( 1e-6 , Unit::ElectricalConductance ); // Siemens (A^2*s^3/kg/m^2) +const Quantity Quantity::MilliSiemens ( 1e-9 , Unit::ElectricalConductance ); +const Quantity Quantity::MicroSiemens ( 1e-12 , Unit::ElectricalConductance ); -const Quantity Quantity::MegaSiemens(1.0, Unit(-2, -1, 3, 2)); -const Quantity Quantity::KiloSiemens(1e-3, Unit(-2, -1, 3, 2)); -const Quantity Quantity::Siemens(1e-6, Unit(-2, -1, 3, 2)); // Siemens (A^2*s^3/kg/m^2) -const Quantity Quantity::MilliSiemens(1e-9, Unit(-2, -1, 3, 2)); -const Quantity Quantity::MicroSiemens(1e-12, Unit(-2, -1, 3, 2)); +const Quantity Quantity::Ohm ( 1e+6 , Unit::ElectricalResistance ); // Ohm (kg*m^2/A^2/s^3) +const Quantity Quantity::KiloOhm ( 1e+9 , Unit::ElectricalResistance ); +const Quantity Quantity::MegaOhm ( 1e+12 , Unit::ElectricalResistance ); -const Quantity Quantity::Ohm(1e+6, Unit(2, 1, -3, -2)); // Ohm (kg*m^2/A^2/s^3) -const Quantity Quantity::KiloOhm(1e+9, Unit(2, 1, -3, -2)); -const Quantity Quantity::MegaOhm(1e+12, Unit(2, 1, -3, -2)); +const Quantity Quantity::Coulomb ( 1.0 , Unit::ElectricCharge ); // Coulomb (A*s) -const Quantity Quantity::Coulomb(1.0, Unit(0, 0, 1, 1)); // Coulomb (A*s) +const Quantity Quantity::Tesla ( 1.0 , Unit::MagneticFluxDensity ); // Tesla (kg/s^2/A) +const Quantity Quantity::Gauss ( 1e-4 , Unit::MagneticFluxDensity ); // 1 G = 1e-4 T -const Quantity Quantity::Tesla(1.0, Unit(0, 1, -2, -1)); // Tesla (kg/s^2/A) -const Quantity Quantity::Gauss(1e-4, Unit(0, 1, -2, -1)); // 1 G = 1e-4 T +const Quantity Quantity::Weber ( 1e6 , Unit::MagneticFlux ); // Weber (kg*m^2/s^2/A) -const Quantity Quantity::Weber(1e6, Unit(2, 1, -2, -1)); // Weber (kg*m^2/s^2/A) +const Quantity Quantity::PicoFarad ( 1e-18 , Unit::ElectricalCapacitance ); +const Quantity Quantity::NanoFarad ( 1e-15 , Unit::ElectricalCapacitance ); +const Quantity Quantity::MicroFarad ( 1e-12 , Unit::ElectricalCapacitance ); +const Quantity Quantity::MilliFarad ( 1e-9 , Unit::ElectricalCapacitance ); +const Quantity Quantity::Farad ( 1e-6 , Unit::ElectricalCapacitance ); // Farad (s^4*A^2/m^2/kg) -// disable Oersted because people need to input e.g. a field strength of -// 1 ampere per meter -> 1 A/m and not get the recalculation to Oersted -// const Quantity Quantity::Oersted(0.07957747, Unit(-1, 0, 0, 1));// Oersted (A/m) +const Quantity Quantity::NanoHenry ( 1e-3 , Unit::ElectricalInductance ); +const Quantity Quantity::MicroHenry ( 1.0 , Unit::ElectricalInductance ); +const Quantity Quantity::MilliHenry ( 1e+3 , Unit::ElectricalInductance ); +const Quantity Quantity::Henry ( 1e+6 , Unit::ElectricalInductance ); // Henry (kg*m^2/s^2/A^2) -const Quantity Quantity::PicoFarad(1e-18, Unit(-2, -1, 4, 2)); -const Quantity Quantity::NanoFarad(1e-15, Unit(-2, -1, 4, 2)); -const Quantity Quantity::MicroFarad(1e-12, Unit(-2, -1, 4, 2)); -const Quantity Quantity::MilliFarad(1e-9, Unit(-2, -1, 4, 2)); -const Quantity Quantity::Farad(1e-6, Unit(-2, -1, 4, 2)); // Farad (s^4*A^2/m^2/kg) +const Quantity Quantity::Joule ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::MilliJoule ( 1e+3 , Unit::Moment ); +const Quantity Quantity::KiloJoule ( 1e+9 , Unit::Moment ); +const Quantity Quantity::NewtonMeter ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::VoltAmpereSecond ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::WattSecond ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::KiloWattHour ( 3.6e+12 , Unit::Moment ); // 1 kWh = 3.6e6 J +const Quantity Quantity::ElectronVolt ( 1.602176634e-13 , Unit::Moment ); // 1 eV = 1.602176634e-19 J +const Quantity Quantity::KiloElectronVolt ( 1.602176634e-10 , Unit::Moment ); +const Quantity Quantity::MegaElectronVolt ( 1.602176634e-7 , Unit::Moment ); +const Quantity Quantity::Calorie ( 4.1868e+6 , Unit::Moment ); // 1 cal = 4.1868 J +const Quantity Quantity::KiloCalorie ( 4.1868e+9 , Unit::Moment ); -const Quantity Quantity::NanoHenry(1e-3, Unit(2, 1, -2, -2)); -const Quantity Quantity::MicroHenry(1.0, Unit(2, 1, -2, -2)); -const Quantity Quantity::MilliHenry(1e+3, Unit(2, 1, -2, -2)); -const Quantity Quantity::Henry(1e+6, Unit(2, 1, -2, -2)); // Henry (kg*m^2/s^2/A^2) - -const Quantity Quantity::Joule(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::MilliJoule(1e+3, Unit(2, 1, -2)); -const Quantity Quantity::KiloJoule(1e+9, Unit(2, 1, -2)); -const Quantity Quantity::NewtonMeter(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::VoltAmpereSecond(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::WattSecond(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::KiloWattHour(3.6e+12, Unit(2, 1, -2)); // 1 kWh = 3.6e6 J -const Quantity Quantity::ElectronVolt(1.602176634e-13, Unit(2, 1, -2)); // 1 eV = 1.602176634e-19 J -const Quantity Quantity::KiloElectronVolt(1.602176634e-10, Unit(2, 1, -2)); -const Quantity Quantity::MegaElectronVolt(1.602176634e-7, Unit(2, 1, -2)); -const Quantity Quantity::Calorie(4.1868e+6, Unit(2, 1, -2)); // 1 cal = 4.1868 J -const Quantity Quantity::KiloCalorie(4.1868e+9, Unit(2, 1, -2)); - -const Quantity Quantity::KMH(277.778, Unit(1, 0, -1)); // km/h -const Quantity Quantity::MPH(447.04, Unit(1, 0, -1)); // Mile/h - -const Quantity Quantity::AngMinute(1.0 / 60.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // angular minute -const Quantity Quantity::AngSecond(1.0 / 3600.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // angular second -const Quantity - Quantity::Degree(1.0, - Unit(0, 0, 0, 0, 0, 0, 0, 1)); // degree (internal standard angle) -const Quantity Quantity::Radian(180 / std::numbers::pi, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // radian -const Quantity Quantity::Gon(360.0 / 400.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // gon +const Quantity Quantity::KMH ( 277.778 , Unit::Velocity ); // km/h +const Quantity Quantity::MPH ( 447.04 , Unit::Velocity ); // Mile/h +const Quantity Quantity::AngMinute ( 1.0 / 60.0 , Unit::Angle ); // angular minute +const Quantity Quantity::AngSecond ( 1.0 / 3600.0 , Unit::Angle ); // angular second +const Quantity Quantity::Degree ( 1.0 , Unit::Angle ); // degree (internal standard angle) +const Quantity Quantity::Radian ( 180 / std::numbers::pi, Unit::Angle ); // radian +const Quantity Quantity::Gon ( 360.0 / 400.0 , Unit::Angle ); // gon +// clang-format on // === Parser & Scanner stuff =============================================== diff --git a/src/Base/QuantityPyImp.cpp b/src/Base/QuantityPyImp.cpp index 1fea8807c6..365ef592f6 100644 --- a/src/Base/QuantityPyImp.cpp +++ b/src/Base/QuantityPyImp.cpp @@ -91,45 +91,37 @@ int QuantityPy::PyInit(PyObject* args, PyObject* /*kwd*/) *self = *(static_cast(object)->getQuantityPtr()); return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + double f = std::numeric_limits::max(); if (PyArg_ParseTuple(args, "dO!", &f, &(UnitPy::Type), &object)) { *self = Quantity(f, *(static_cast(object)->getUnitPtr())); return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + if (PyArg_ParseTuple(args, "dO!", &f, &(QuantityPy::Type), &object)) { PyErr_SetString(PyExc_TypeError, "Second argument must be a Unit not a Quantity"); return -1; } - - int i1 = 0; - int i2 = 0; - int i3 = 0; - int i4 = 0; - int i5 = 0; - int i6 = 0; - int i7 = 0; - int i8 = 0; PyErr_Clear(); // set by PyArg_ParseTuple() + + int i1 {0}; + int i2 {0}; + int i3 {0}; + int i4 {0}; + int i5 {0}; + int i6 {0}; + int i7 {0}; + int i8 {0}; if (PyArg_ParseTuple(args, "|diiiiiiii", &f, &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) { if (f < std::numeric_limits::max()) { - *self = Quantity(f, - Unit {static_cast(i1), - static_cast(i2), - static_cast(i3), - static_cast(i4), - static_cast(i5), - static_cast(i6), - static_cast(i7), - static_cast(i8)}); + *self = Quantity {f, Unit(i1, i2, i3, i4, i5, i6, i7, i8)}; } return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + char* string {}; if (PyArg_ParseTuple(args, "et", "utf-8", &string)) { std::string str(string); @@ -144,13 +136,13 @@ int QuantityPy::PyInit(PyObject* args, PyObject* /*kwd*/) return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + if (PyArg_ParseTuple(args, "det", &f, "utf-8", &string)) { - std::string unit(string); + std::string str(string); PyMem_Free(string); try { - *self = Quantity(f, unit); + *self = Quantity(f, str); } catch (const ParserError& e) { PyErr_SetString(PyExc_ValueError, e.what()); @@ -210,7 +202,7 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const }; auto tryUnitPartsAndValue = [&]() -> std::optional { - double f = std::numeric_limits::max(); + double f; int i1 {0}; int i2 {0}; int i3 {0}; @@ -219,20 +211,11 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const int i6 {0}; int i7 {0}; int i8 {0}; - PyErr_Clear(); if (!PyArg_ParseTuple(args, "d|iiiiiiii", &f, &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) { return std::nullopt; } - if (f >= std::numeric_limits::max()) { - return std::nullopt; - } - - auto re = [](auto val) { - return static_cast(val); - }; - - return Quantity {f, Unit {re(i1), re(i2), re(i3), re(i4), re(i5), re(i6), re(i7), re(i8)}}; + return Quantity {f, Unit(i1, i2, i3, i4, i5, i6, i7, i8)}; }; auto tryString = [&]() -> std::optional { diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index 8b8570879a..45a4af42e7 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -1,23 +1,22 @@ -/*************************************************************************** - * Copyright (c) 2011 Jürgen Riegel * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * ***************************************************************************/ #include "PreCompiled.h" @@ -27,698 +26,349 @@ #include #include #include -#include +#include +#include #endif -#include "Unit.h" -#include "Exception.h" -#include "Quantity.h" +#include +#include +#include "Unit.h" using namespace Base; -// clang-format off -constexpr int UnitSignatureLengthBits = 4; -constexpr int UnitSignatureMassBits = 4; -constexpr int UnitSignatureTimeBits = 4; -constexpr int UnitSignatureElectricCurrentBits = 4; -constexpr int UnitSignatureThermodynamicTemperatureBits = 4; -constexpr int UnitSignatureAmountOfSubstanceBits = 4; -constexpr int UnitSignatureLuminousIntensityBits = 4; -constexpr int UnitSignatureAngleBits = 4; - -struct UnitSignature { - int32_t Length: UnitSignatureLengthBits; - int32_t Mass: UnitSignatureMassBits; - int32_t Time: UnitSignatureTimeBits; - int32_t ElectricCurrent: UnitSignatureElectricCurrentBits; - int32_t ThermodynamicTemperature: UnitSignatureThermodynamicTemperatureBits; - int32_t AmountOfSubstance: UnitSignatureAmountOfSubstanceBits; - int32_t LuminousIntensity: UnitSignatureLuminousIntensityBits; - int32_t Angle: UnitSignatureAngleBits; +struct UnitSpec +{ + std::string_view name; + UnitExponents exps; }; -static inline uint32_t sigVal(const std::string &op, - int length, int mass, int time, int electricCurrent, - int thermodynamicTemperature, int amountOfSubstance, int luminousIntensity, int angle) +constexpr auto unitSpecs = std::to_array({ + // clang-format off + // Length + // . Mass + // . . Time + // . . . ElectricCurrent + // . . . . ThermodynamicTemperature + // . . . . . AmountOfSubstance + // . . . . . . LuminousIntensity + // . . . . . . . Angle + { "1" , { 0, 0, 0, 0, 0, 0, 0, 0 } }, + { "Length" , { 1 } }, + { "Mass" , { 0, 1 } }, + { "TimeSpan" , { 0, 0, 1 } }, + { "ElectricCurrent" , { 0, 0, 0, 1 } }, + { "Temperature" , { 0, 0, 0, 0, 1 } }, + { "AmountOfSubstance" , { 0, 0, 0, 0, 0, 1 } }, + { "LuminousIntensity" , { 0, 0, 0, 0, 0, 0, 1 } }, + { "Angle" , { 0, 0, 0, 0, 0, 0, 0, 1 } }, + { "Acceleration" , { 1, 0, -2 } }, + { "AngleOfFriction" , { 0, 0, 0, 0, 0, 0, 0, 1 } }, + { "Area" , { 2 } }, + { "CurrentDensity" , { -2, 0, 0, 1 } }, + { "Density" , { -3, 1 } }, + { "DissipationRate" , { 2, 0, -3 } }, + { "DynamicViscosity" , { -1, 1, -1 } }, + { "ElectricalCapacitance" , { -2, -1, 4, 2 } }, + { "ElectricalConductance" , { -2, -1, 3, 2 } }, + { "ElectricalConductivity" , { -3, -1, 3, 2 } }, + { "ElectricalInductance" , { 2, 1, -2, -2 } }, + { "ElectricalResistance" , { 2, 1, -3, -2 } }, + { "ElectricCharge" , { 0, 0, 1, 1 } }, + { "ElectricPotential" , { 2, 1, -3, -1 } }, + { "ElectromagneticPotential" , { 1, 1, -2, -1 } }, + { "Force" , { 1, 1, -2 } }, + { "Frequency" , { 0, 0, -1 } }, + { "HeatFlux" , { 0, 1, -3 } }, + { "InverseArea" , { -2 } }, + { "InverseLength" , { -1 } }, + { "InverseVolume" , { -3 } }, + { "KinematicViscosity" , { 2, 0, -1 } }, + { "MagneticFieldStrength" , { -1, 0, 0, 1 } }, + { "MagneticFlux" , { 2, 1, -2, -1 } }, + { "MagneticFluxDensity" , { 0, 1, -2, -1 } }, + { "Magnetization" , { -1, 0, 0, 1 } }, + { "Moment" , { 2, 1, -2 } }, + { "Pressure" , { -1, 1, -2 } }, + { "Power" , { 2, 1, -3 } }, + { "ShearModulus" , { -1, 1, -2 } }, + { "SpecificEnergy" , { 2, 0, -2 } }, + { "SpecificHeat" , { 2, 0, -2, 0, -1 } }, + { "Stiffness" , { 0, 1, -2 } }, + { "StiffnessDensity" , { -2, 1, -2 } }, + { "Stress" , { -1, 1, -2 } }, + { "SurfaceChargeDensity" , { -2, 0, 1, 1 } }, + { "ThermalConductivity" , { 1, 1, -3, 0, -1 } }, + { "ThermalExpansionCoefficient" , { 0, 0, 0, 0, -1 } }, + { "ThermalTransferCoefficient" , { 0, 1, -3, 0, -1 } }, + { "UltimateTensileStrength" , { -1, 1, -2 } }, + { "VacuumPermittivity" , { -3, -1, 4, 2 } }, + { "Velocity" , { 1, 0, -1 } }, + { "Volume" , { 3 } }, + { "VolumeChargeDensity" , { -3, 0, 1, 1 } }, + { "VolumeFlowRate" , { 3, 0, -1 } }, + { "VolumetricThermalExpansionCoefficient" , { 0, 0, 0, 0, -1 } }, + { "Work" , { 2, 1, -2 } }, + { "YieldStrength" , { -1, 1, -2 } }, + { "YoungsModulus" , { -1, 1, -2 } }, +}); // clang-format on + +Unit::Unit(const int length, // NOLINT + const int mass, + const int time, + const int electricCurrent, + const int thermodynamicTemperature, + const int amountOfSubstance, + const int luminousIntensity, + const int angle) + : _name {""} { - if ( ( length >= (1 << (UnitSignatureLengthBits - 1)) ) || - ( mass >= (1 << (UnitSignatureMassBits - 1)) ) || - ( time >= (1 << (UnitSignatureTimeBits - 1)) ) || - ( electricCurrent >= (1 << (UnitSignatureElectricCurrentBits - 1)) ) || - ( thermodynamicTemperature >= (1 << (UnitSignatureThermodynamicTemperatureBits - 1)) ) || - ( amountOfSubstance >= (1 << (UnitSignatureAmountOfSubstanceBits - 1)) ) || - ( luminousIntensity >= (1 << (UnitSignatureLuminousIntensityBits - 1)) ) || - ( angle >= (1 << (UnitSignatureAngleBits - 1)) ) ) { - throw Base::OverflowError(("Unit overflow in " + op).c_str()); - } - if ( ( length < -(1 << (UnitSignatureLengthBits - 1)) ) || - ( mass < -(1 << (UnitSignatureMassBits - 1)) ) || - ( time < -(1 << (UnitSignatureTimeBits - 1)) ) || - ( electricCurrent < -(1 << (UnitSignatureElectricCurrentBits - 1)) ) || - ( thermodynamicTemperature < -(1 << (UnitSignatureThermodynamicTemperatureBits - 1)) ) || - ( amountOfSubstance < -(1 << (UnitSignatureAmountOfSubstanceBits - 1)) ) || - ( luminousIntensity < -(1 << (UnitSignatureLuminousIntensityBits - 1)) ) || - ( angle < -(1 << (UnitSignatureAngleBits - 1)) ) ) { - throw Base::UnderflowError(("Unit underflow in " + op).c_str()); - } - - UnitSignature Sig; - Sig.Length = length; - Sig.Mass = mass; - Sig.Time = time; - Sig.ElectricCurrent = electricCurrent; - Sig.ThermodynamicTemperature = thermodynamicTemperature; - Sig.AmountOfSubstance = amountOfSubstance; - Sig.LuminousIntensity = luminousIntensity; - Sig.Angle = angle; - - uint32_t ret; - memcpy(&ret, &Sig, sizeof(ret)); - return ret; -} - - -Unit::Unit(int8_t Length, //NOLINT - int8_t Mass, - int8_t Time, - int8_t ElectricCurrent, - int8_t ThermodynamicTemperature, - int8_t AmountOfSubstance, - int8_t LuminousIntensity, - int8_t Angle) -{ - Val = sigVal("unit", - Length, - Mass, - Time, - ElectricCurrent, - ThermodynamicTemperature, - AmountOfSubstance, - LuminousIntensity, - Angle); -} - - -Unit::Unit() //NOLINT -{ - Val = 0; -} - -Unit::Unit(const std::string& expr) // NOLINT -{ - try { - *this = Quantity::parse(expr).getUnit(); - } - catch (const Base::ParserError&) { - Val = 0; - } -} - -Unit Unit::pow(double exp) const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - auto isInt = [](double value) { - return std::fabs(std::round(value) - value) < std::numeric_limits::epsilon(); + auto cast = [](auto val) { + return static_cast(std::clamp(val, + std::numeric_limits::min(), + std::numeric_limits::max())); }; - if (!isInt(sig.Length * exp) || - !isInt(sig.Mass * exp) || - !isInt(sig.Time * exp) || - !isInt(sig.ElectricCurrent * exp) || - !isInt(sig.ThermodynamicTemperature * exp) || - !isInt(sig.AmountOfSubstance * exp) || - !isInt(sig.LuminousIntensity * exp) || - !isInt(sig.Angle * exp)) - throw Base::UnitsMismatchError("pow() of unit not possible"); - Unit result; - result.Val = sigVal("pow()", - sig.Length * exp, - sig.Mass * exp, - sig.Time * exp, - sig.ElectricCurrent * exp, - sig.ThermodynamicTemperature * exp, - sig.AmountOfSubstance * exp, - sig.LuminousIntensity * exp, - sig.Angle * exp); + _exps[0] = cast(length); + _exps[1] = cast(mass); + _exps[2] = cast(time); + _exps[3] = cast(electricCurrent); + _exps[4] = cast(thermodynamicTemperature); + _exps[5] = cast(amountOfSubstance); + _exps[6] = cast(luminousIntensity); + _exps[7] = cast(angle); - return result; + checkRange(); } -Unit Unit::sqrt() const +bool Unit::operator==(const Unit& that) const { - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - // All components of unit must be either zero or dividable by 2 - if (!((sig.Length % 2) == 0) && - ((sig.Mass % 2) == 0) && - ((sig.Time % 2) == 0) && - ((sig.ElectricCurrent % 2) == 0) && - ((sig.ThermodynamicTemperature % 2) == 0) && - ((sig.AmountOfSubstance % 2) == 0) && - ((sig.LuminousIntensity % 2) == 0) && - ((sig.Angle % 2) == 0)) - throw Base::UnitsMismatchError("sqrt() needs even dimensions"); - - Unit result; - result.Val = sigVal("sqrt()", - sig.Length >> 1, - sig.Mass >> 1, - sig.Time >> 1, - sig.ElectricCurrent >> 1, - sig.ThermodynamicTemperature >> 1, - sig.AmountOfSubstance >> 1, - sig.LuminousIntensity >> 1, - sig.Angle >> 1); - - return result; + return _exps == that._exps; } -Unit Unit::cbrt() const +bool Unit::operator!=(const Unit& that) const { - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - // All components of unit must be either zero or dividable by 3 - if (!((sig.Length % 3) == 0) && - ((sig.Mass % 3) == 0) && - ((sig.Time % 3) == 0) && - ((sig.ElectricCurrent % 3) == 0) && - ((sig.ThermodynamicTemperature % 3) == 0) && - ((sig.AmountOfSubstance % 3) == 0) && - ((sig.LuminousIntensity % 3) == 0) && - ((sig.Angle % 3) == 0)) - throw Base::UnitsMismatchError("cbrt() needs dimensions to be multiples of 3"); + return _exps != that._exps; +} - Unit result; - result.Val = sigVal("cbrt()", - sig.Length / 3, - sig.Mass / 3, - sig.Time / 3, - sig.ElectricCurrent / 3, - sig.ThermodynamicTemperature / 3, - sig.AmountOfSubstance / 3, - sig.LuminousIntensity / 3, - sig.Angle / 3); +Unit& Unit::operator*=(const Unit& that) +{ + *this = *this * that; + return *this; +} - return result; +Unit& Unit::operator/=(const Unit& that) +{ + *this = *this / that; + return *this; +} + +Unit Unit::operator*(const Unit& right) const +{ + auto mult = [&](auto leftExponent, auto rightExponent) { + return leftExponent + rightExponent; + }; + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), right._exps.begin(), res.begin(), mult); + + return Unit {res}; +} + +Unit Unit::operator/(const Unit& right) const +{ + auto div = [&](auto leftExponent, auto rightExponent) { + return leftExponent - rightExponent; + }; + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), right._exps.begin(), res.begin(), div); + + return Unit {res}; +} + +Unit Unit::root(const uint8_t num) const +{ + auto apply = [&](auto val) { + if (val % num != 0) { + throw UnitsMismatchError("unit values must be divisible by root"); + } + return static_cast(val / num); + }; + + if (num < 1) { + throw UnitsMismatchError("root must be > 0"); + } + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), res.begin(), apply); + + return Unit {res}; +} + +Unit Unit::pow(const double exp) const +{ + auto apply = [&](const auto val) { + const auto num {val * exp}; + if (std::fabs(std::round(num) - num) >= std::numeric_limits::epsilon()) { + throw UnitsMismatchError("pow() of unit not possible"); + } + + return static_cast(val * exp); + }; + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), res.begin(), apply); + + return Unit {res}; +} + +UnitExponents Unit::exponents() const +{ + return _exps; } int Unit::length() const { - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Length; -} - -int Unit::mass() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Mass; -} - - -int Unit::time() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Time; -} - -int Unit::electricCurrent() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.ElectricCurrent; -} - -int Unit::thermodynamicTemperature() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.ThermodynamicTemperature; -} - -int Unit::amountOfSubstance() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.AmountOfSubstance; -} - -int Unit::luminousIntensity() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.LuminousIntensity; -} - -int Unit::angle() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Angle; -} - -int Unit::operator [](int index) const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - - switch (index) { - case 0: - return sig.Length; - case 1: - return sig.Mass; - case 2: - return sig.Time; - case 3: - return sig.ElectricCurrent; - case 4: - return sig.ThermodynamicTemperature; - case 5: - return sig.AmountOfSubstance; - case 6: - return sig.LuminousIntensity; - case 7: - return sig.Angle; - default: - throw Base::IndexError("Unknown Unit element"); - } -} - -bool Unit::operator ==(const Unit& that) const -{ - return Val == that.Val; -} - -bool Unit::operator !=(const Unit& that) const -{ - return Val != that.Val; -} - -Unit Unit::operator *(const Unit &right) const -{ - Unit result; - UnitSignature sig, rsig; - - memcpy(&sig, &Val, sizeof(Val)); - memcpy(&rsig, &right.Val, sizeof(right.Val)); - result.Val = sigVal("* operator", - sig.Length + rsig.Length, - sig.Mass + rsig.Mass, - sig.Time + rsig.Time, - sig.ElectricCurrent + rsig.ElectricCurrent, - sig.ThermodynamicTemperature + rsig.ThermodynamicTemperature, - sig.AmountOfSubstance + rsig.AmountOfSubstance, - sig.LuminousIntensity + rsig.LuminousIntensity, - sig.Angle + rsig.Angle); - - return result; -} - -Unit Unit::operator /(const Unit &right) const -{ - Unit result; - UnitSignature sig, rsig; - - memcpy(&sig, &Val, sizeof(Val)); - memcpy(&rsig, &right.Val, sizeof(right.Val)); - result.Val = sigVal("/ operator", - sig.Length - rsig.Length, - sig.Mass - rsig.Mass, - sig.Time - rsig.Time, - sig.ElectricCurrent - rsig.ElectricCurrent, - sig.ThermodynamicTemperature - rsig.ThermodynamicTemperature, - sig.AmountOfSubstance - rsig.AmountOfSubstance, - sig.LuminousIntensity - rsig.LuminousIntensity, - sig.Angle - rsig.Angle); - - return result; + return _exps[0]; } std::string Unit::getString() const { - if (Val == 0) { - return {}; + auto buildSubStr = [&](auto index) { + const std::string unitStrString {unitSymbols.at(index)}; + const auto absol {abs(_exps.at(index))}; + + return absol <= 1 ? unitStrString + : fmt::format("{}^{}", unitStrString, std::to_string(absol)); + }; + + auto buildStr = [&](auto indexes) { + std::vector subStrings {}; + std::transform(indexes.begin(), indexes.end(), std::back_inserter(subStrings), buildSubStr); + + return fmt::format("{}", fmt::join(subStrings, "*")); + }; + + //------------------------------------------------------------------------------ + + auto [posValIndexes, negValIndexes] = nonZeroValsIndexes(); + auto numeratorStr = buildStr(posValIndexes); + if (negValIndexes.empty()) { + return numeratorStr; } - std::stringstream ret; - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); + auto denominatorStr = buildStr(negValIndexes); - if (sig.Length > 0 || - sig.Mass > 0 || - sig.Time > 0 || - sig.ElectricCurrent > 0 || - sig.ThermodynamicTemperature > 0 || - sig.AmountOfSubstance > 0 || - sig.LuminousIntensity > 0 || - sig.Angle > 0 ) { + return fmt::format("{}/{}", + numeratorStr.empty() ? "1" : numeratorStr, + negValIndexes.size() > 1 ? fmt::format("({})", denominatorStr) + : denominatorStr); +} - bool mult = false; - if (sig.Length > 0) { - mult = true; - ret << "mm"; - if (sig.Length > 1) { - ret << "^" << sig.Length; - } - } - - if (sig.Mass > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "kg"; - if (sig.Mass > 1) { - ret << "^" << sig.Mass; - } - } - - if (sig.Time > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "s"; - if (sig.Time > 1) { - ret << "^" << sig.Time; - } - } - - if (sig.ElectricCurrent > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "A"; - if (sig.ElectricCurrent > 1) { - ret << "^" << sig.ElectricCurrent; - } - } - - if (sig.ThermodynamicTemperature > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "K"; - if (sig.ThermodynamicTemperature > 1) { - ret << "^" << sig.ThermodynamicTemperature; - } - } - - if (sig.AmountOfSubstance > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "mol"; - if (sig.AmountOfSubstance > 1) { - ret << "^" << sig.AmountOfSubstance; - } - } - - if (sig.LuminousIntensity > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "cd"; - if (sig.LuminousIntensity > 1) { - ret << "^" << sig.LuminousIntensity; - } - } - - if (sig.Angle > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "deg"; - if (sig.Angle > 1) { - ret << "^" << sig.Angle; - } - } - } - else { - ret << "1"; - } - - if (sig.Length < 0 || - sig.Mass < 0 || - sig.Time < 0 || - sig.ElectricCurrent < 0 || - sig.ThermodynamicTemperature < 0 || - sig.AmountOfSubstance < 0 || - sig.LuminousIntensity < 0 || - sig.Angle < 0 ) { - ret << "/"; - - int nnom = 0; - nnom += sig.Length < 0 ? 1 : 0; - nnom += sig.Mass < 0 ? 1 : 0; - nnom += sig.Time < 0 ? 1 : 0; - nnom += sig.ElectricCurrent < 0 ? 1 : 0; - nnom += sig.ThermodynamicTemperature < 0 ? 1 : 0; - nnom += sig.AmountOfSubstance < 0 ? 1 : 0; - nnom += sig.LuminousIntensity < 0 ? 1 : 0; - nnom += sig.Angle < 0 ? 1 : 0; - - if (nnom > 1) { - ret << '('; - } - - bool mult = false; - if (sig.Length < 0) { - ret << "mm"; - mult = true; - if (sig.Length < -1) { - ret << "^" << abs(sig.Length); - } - } - - if (sig.Mass < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "kg"; - if (sig.Mass < -1) { - ret << "^" << abs(sig.Mass); - } - } - - if (sig.Time < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "s"; - if (sig.Time < -1) { - ret << "^" << abs(sig.Time); - } - } - - if (sig.ElectricCurrent < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "A"; - if (sig.ElectricCurrent < -1) { - ret << "^" << abs(sig.ElectricCurrent); - } - } - - if (sig.ThermodynamicTemperature < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "K"; - if (sig.ThermodynamicTemperature < -1) { - ret << "^" << abs(sig.ThermodynamicTemperature); - } - } - - if (sig.AmountOfSubstance < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "mol"; - if (sig.AmountOfSubstance < -1) { - ret << "^" << abs(sig.AmountOfSubstance); - } - } - - if (sig.LuminousIntensity < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "cd"; - if (sig.LuminousIntensity < -1) { - ret << "^" << abs(sig.LuminousIntensity); - } - } - - if (sig.Angle < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "deg"; - if (sig.Angle < -1) { - ret << "^" << abs(sig.Angle); - } - } - - if (nnom > 1) { - ret << ')'; - } - } - - return ret.str(); +std::string Unit::representation() const +{ + auto name = getTypeString(); + auto inParen = fmt::format("Unit: {} ({})", getString(), fmt::join(_exps, ",")); + return name.empty() ? inParen : fmt::format("{} [{}]", inParen, name); } std::string Unit::getTypeString() const { - static std::array, 57> unitSpecs {{ - { Unit::Acceleration, "Acceleration" }, - { Unit::AmountOfSubstance, "AmountOfSubstance" }, - { Unit::Angle, "Angle" }, - { Unit::AngleOfFriction, "AngleOfFriction" }, - { Unit::Area, "Area" }, - { Unit::CurrentDensity, "CurrentDensity" }, - { Unit::Density, "Density" }, - { Unit::DissipationRate, "DissipationRate" }, - { Unit::DynamicViscosity, "DynamicViscosity" }, - { Unit::ElectricalCapacitance, "ElectricalCapacitance" }, - { Unit::ElectricalConductance, "ElectricalConductance" }, - { Unit::ElectricalConductivity, "ElectricalConductivity" }, - { Unit::ElectricalInductance, "ElectricalInductance" }, - { Unit::ElectricalResistance, "ElectricalResistance" }, - { Unit::ElectricCharge, "ElectricCharge" }, - { Unit::SurfaceChargeDensity, "SurfaceChargeDensity" }, - { Unit::VolumeChargeDensity, "VolumeChargeDensity" }, - { Unit::ElectricCurrent, "ElectricCurrent" }, - { Unit::ElectricPotential, "ElectricPotential" }, - { Unit::ElectromagneticPotential, "ElectromagneticPotential" }, - { Unit::Frequency, "Frequency" }, - { Unit::Force, "Force" }, - { Unit::HeatFlux, "HeatFlux" }, - { Unit::InverseArea, "InverseArea" }, - { Unit::InverseLength, "InverseLength" }, - { Unit::InverseVolume, "InverseVolume" }, - { Unit::KinematicViscosity, "KinematicViscosity" }, - { Unit::Length, "Length" }, - { Unit::LuminousIntensity, "LuminousIntensity" }, - { Unit::MagneticFieldStrength, "MagneticFieldStrength" }, - { Unit::MagneticFlux, "MagneticFlux" }, - { Unit::MagneticFluxDensity, "MagneticFluxDensity" }, - { Unit::Magnetization, "Magnetization" }, - { Unit::Mass, "Mass" }, - { Unit::Pressure, "Pressure" }, - { Unit::Power, "Power" }, - { Unit::ShearModulus, "ShearModulus" }, - { Unit::SpecificEnergy, "SpecificEnergy" }, - { Unit::SpecificHeat, "SpecificHeat" }, - { Unit::Stiffness, "Stiffness" }, - { Unit::StiffnessDensity, "StiffnessDensity" }, - { Unit::Stress, "Stress" }, - { Unit::Temperature, "Temperature" }, - { Unit::ThermalConductivity, "ThermalConductivity" }, - { Unit::ThermalExpansionCoefficient, "ThermalExpansionCoefficient" }, - { Unit::ThermalTransferCoefficient, "ThermalTransferCoefficient" }, - { Unit::TimeSpan, "TimeSpan" }, - { Unit::UltimateTensileStrength, "UltimateTensileStrength" }, - { Unit::VacuumPermittivity, "VacuumPermittivity" }, - { Unit::Velocity, "Velocity" }, - { Unit::Volume, "Volume" }, - { Unit::VolumeFlowRate, "VolumeFlowRate" }, - { Unit::VolumetricThermalExpansionCoefficient, "VolumetricThermalExpansionCoefficient" }, - { Unit::Work, "Work" }, - { Unit::YieldStrength, "YieldStrength" }, - { Unit::YoungsModulus, "YoungsModulus" }, - { Unit::Moment, "Moment" }, - }}; + if (_name.empty()) { + const auto spec = std::ranges::find(unitSpecs, _exps, &UnitSpec::exps); + return std::string(spec == unitSpecs.end() ? "" : spec->name); + } - const auto spec = - std::find_if(unitSpecs.begin(), unitSpecs.end(), [&](const auto& pair) { - return pair.first == *this; - }); - - if (spec == std::end(unitSpecs)) - return ""; - - return spec->second; + return std::string {_name.data(), _name.size()}; } -const Unit Unit::One (0, 0, 0, 0, 0, 0, 0, 0); +std::pair, std::vector> Unit::nonZeroValsIndexes() const +{ + std::vector pos {}; + std::vector neg {}; -// SI base units -const Unit Unit::AmountOfSubstance (0, 0, 0, 0, 0, 1); -const Unit Unit::ElectricCurrent (0, 0, 0, 1); -const Unit Unit::Length (1); -const Unit Unit::LuminousIntensity (0, 0, 0, 0, 0, 0, 1); -const Unit Unit::Mass (0, 1); -const Unit Unit::Temperature (0, 0, 0, 0, 1); -const Unit Unit::TimeSpan (0, 0, 1); + auto posNeg = [&, index {0}](auto val) mutable { + if (val != 0) { + (val > 0 ? pos : neg).push_back(index); + } + ++index; + }; -// all other units -const Unit Unit::Acceleration (1, 0, -2); -const Unit Unit::Angle (0, 0, 0, 0, 0, 0, 0, 1); -const Unit Unit::AngleOfFriction (0, 0, 0, 0, 0, 0, 0, 1); -const Unit Unit::Area (2); -const Unit Unit::CompressiveStrength (-1, 1, -2); -const Unit Unit::CurrentDensity (-2, 0, 0, 1); -const Unit Unit::Density (-3, 1); -const Unit Unit::DissipationRate (2, 0, -3); // https://cfd-online.com/Wiki/Turbulence_dissipation_rate -const Unit Unit::DynamicViscosity (-1, 1, -1); -const Unit Unit::ElectricalCapacitance (-2, -1, 4, 2); -const Unit Unit::ElectricalConductance (-2, -1, 3, 2); -const Unit Unit::ElectricalConductivity (-3, -1, 3, 2); -const Unit Unit::ElectricalInductance (2, 1, -2, -2); -const Unit Unit::ElectricalResistance (2, 1, -3, -2); -const Unit Unit::ElectricCharge (0, 0, 1, 1); -const Unit Unit::SurfaceChargeDensity (-2, 0, 1, 1); -const Unit Unit::VolumeChargeDensity (-3, 0, 1, 1); -const Unit Unit::ElectricPotential (2, 1, -3, -1); -const Unit Unit::ElectromagneticPotential (1, 1, -2, -1); -const Unit Unit::Force (1, 1, -2); -const Unit Unit::Frequency (0, 0, -1); -const Unit Unit::HeatFlux (0, 1, -3, 0, 0); -const Unit Unit::InverseArea (-2, 0, 0); -const Unit Unit::InverseLength (-1, 0, 0); -const Unit Unit::InverseVolume (-3, 0, 0); -const Unit Unit::KinematicViscosity (2, 0, -1); -const Unit Unit::MagneticFieldStrength (-1,0,0,1); -const Unit Unit::MagneticFlux (2,1,-2,-1); -const Unit Unit::MagneticFluxDensity (0,1,-2,-1); -const Unit Unit::Magnetization (-1,0,0,1); -const Unit Unit::Moment (2, 1, -2); -const Unit Unit::Pressure (-1,1,-2); -const Unit Unit::Power (2, 1, -3); -const Unit Unit::ShearModulus (-1,1,-2); -const Unit Unit::SpecificEnergy (2, 0, -2); -const Unit Unit::SpecificHeat (2, 0, -2, 0, -1); -const Unit Unit::Stiffness (0, 1, -2); -const Unit Unit::StiffnessDensity (-2, 1, -2); -const Unit Unit::Stress (-1,1,-2); -const Unit Unit::ThermalConductivity (1, 1, -3, 0, -1); -const Unit Unit::ThermalExpansionCoefficient(0, 0, 0, 0, -1); -const Unit Unit::ThermalTransferCoefficient (0, 1, -3, 0, -1); -const Unit Unit::UltimateTensileStrength (-1,1,-2); -const Unit Unit::VacuumPermittivity (-3, -1, 4, 2); -const Unit Unit::Velocity (1, 0, -1); -const Unit Unit::Volume (3); -const Unit Unit::VolumeFlowRate (3, 0, -1); -const Unit Unit::VolumetricThermalExpansionCoefficient(0, 0, 0, 0, -1); -const Unit Unit::Work (2, 1, -2); -const Unit Unit::YieldStrength (-1,1,-2); -const Unit Unit::YoungsModulus (-1,1,-2); -// clang-format on + std::ranges::for_each(_exps, posNeg); + + return {pos, neg}; +} + + +constexpr Unit make(const std::string_view name) +{ + if (const auto spec = std::ranges::find(unitSpecs, name, &UnitSpec::name); + spec != unitSpecs.end()) { + return Unit {spec->exps, spec->name}; + } + throw NameError("Invalid unit name"); +} + +// clang-format off +constexpr Unit Unit::One = make("1" ); + +constexpr Unit Unit::Length = make("Length" ); +constexpr Unit Unit::Mass = make("Mass" ); +constexpr Unit Unit::TimeSpan = make("TimeSpan" ); +constexpr Unit Unit::ElectricCurrent = make("ElectricCurrent" ); +constexpr Unit Unit::Temperature = make("Temperature" ); +constexpr Unit Unit::AmountOfSubstance = make("AmountOfSubstance" ); +constexpr Unit Unit::LuminousIntensity = make("LuminousIntensity" ); +constexpr Unit Unit::Angle = make("Angle" ); + +constexpr Unit Unit::Acceleration = make("Acceleration" ); +constexpr Unit Unit::AngleOfFriction = make("Angle" ); +constexpr Unit Unit::Area = make("Area" ); +constexpr Unit Unit::CompressiveStrength = make("Pressure" ); +constexpr Unit Unit::CurrentDensity = make("CurrentDensity" ); +constexpr Unit Unit::Density = make("Density" ); +constexpr Unit Unit::DissipationRate = make("DissipationRate" ); +constexpr Unit Unit::DynamicViscosity = make("DynamicViscosity" ); +constexpr Unit Unit::ElectricalCapacitance = make("ElectricalCapacitance" ); +constexpr Unit Unit::ElectricalConductance = make("ElectricalConductance" ); +constexpr Unit Unit::ElectricalConductivity = make("ElectricalConductivity" ); +constexpr Unit Unit::ElectricalInductance = make("ElectricalInductance" ); +constexpr Unit Unit::ElectricalResistance = make("ElectricalResistance" ); +constexpr Unit Unit::ElectricCharge = make("ElectricCharge" ); +constexpr Unit Unit::ElectricPotential = make("ElectricPotential" ); +constexpr Unit Unit::ElectromagneticPotential = make("ElectromagneticPotential" ); +constexpr Unit Unit::Force = make("Force" ); +constexpr Unit Unit::Frequency = make("Frequency" ); +constexpr Unit Unit::HeatFlux = make("HeatFlux" ); +constexpr Unit Unit::InverseArea = make("InverseArea" ); +constexpr Unit Unit::InverseLength = make("InverseLength" ); +constexpr Unit Unit::InverseVolume = make("InverseVolume" ); +constexpr Unit Unit::KinematicViscosity = make("KinematicViscosity" ); +constexpr Unit Unit::MagneticFieldStrength = make("Magnetization" ); +constexpr Unit Unit::MagneticFlux = make("MagneticFlux" ); +constexpr Unit Unit::MagneticFluxDensity = make("MagneticFluxDensity" ); +constexpr Unit Unit::Magnetization = make("MagneticFieldStrength" ); +constexpr Unit Unit::Moment = make("Moment" ); +constexpr Unit Unit::Pressure = make("Pressure" ); +constexpr Unit Unit::Power = make("Power" ); +constexpr Unit Unit::ShearModulus = make("Pressure" ); +constexpr Unit Unit::SpecificEnergy = make("SpecificEnergy" ); +constexpr Unit Unit::SpecificHeat = make("SpecificHeat" ); +constexpr Unit Unit::Stiffness = make("Stiffness" ); +constexpr Unit Unit::StiffnessDensity = make("StiffnessDensity" ); +constexpr Unit Unit::Stress = make("Pressure" ); +constexpr Unit Unit::SurfaceChargeDensity = make("SurfaceChargeDensity" ); +constexpr Unit Unit::ThermalConductivity = make("ThermalConductivity" ); +constexpr Unit Unit::ThermalExpansionCoefficient = make("ThermalExpansionCoefficient" ); +constexpr Unit Unit::ThermalTransferCoefficient = make("ThermalTransferCoefficient" ); +constexpr Unit Unit::UltimateTensileStrength = make("Pressure" ); +constexpr Unit Unit::VacuumPermittivity = make("VacuumPermittivity" ); +constexpr Unit Unit::Velocity = make("Velocity" ); +constexpr Unit Unit::Volume = make("Volume" ); +constexpr Unit Unit::VolumeChargeDensity = make("VolumeChargeDensity" ); +constexpr Unit Unit::VolumeFlowRate = make("VolumeFlowRate" ); +constexpr Unit Unit::VolumetricThermalExpansionCoefficient = make("ThermalExpansionCoefficient" ); +constexpr Unit Unit::Work = make("Work" ); +constexpr Unit Unit::YieldStrength = make("Pressure" ); +constexpr Unit Unit::YoungsModulus = make("Pressure" ); diff --git a/src/Base/Unit.h b/src/Base/Unit.h index f5e6197bf2..36d93d6e45 100644 --- a/src/Base/Unit.h +++ b/src/Base/Unit.h @@ -1,178 +1,178 @@ -/*************************************************************************** - * Copyright (c) 2011 Jürgen Riegel * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * ***************************************************************************/ - #ifndef BASE_Unit_H #define BASE_Unit_H +#include #include +#include #include +#include +#include + #include +#include "Exception.h" + namespace Base { -/** - * The Unit class. - */ -class BaseExport Unit +constexpr auto unitSymbols = + std::to_array({"mm", "kg", "s", "A", "K", "mol", "cd", "deg"}); + +constexpr auto unitNumExponents {unitSymbols.size()}; +using UnitExponents = std::array; + +constexpr auto unitExponentLimit {8}; + +class BaseExport Unit final { public: - /// default constructor - explicit Unit(int8_t Length, - int8_t Mass = 0, - int8_t Time = 0, - int8_t ElectricCurrent = 0, - int8_t ThermodynamicTemperature = 0, - int8_t AmountOfSubstance = 0, - int8_t LuminousIntensity = 0, - int8_t Angle = 0); - Unit(); - Unit(const Unit&) = default; - Unit(Unit&&) = default; - explicit Unit(const std::string& expr); - /// Destruction - ~Unit() = default; + Unit() = default; + + explicit constexpr Unit(const UnitExponents exps, const std::string_view name = "") + : _exps {exps} + , _name {name} + { + checkRange(); + } + + /// helper constructor to ease Unit construction from Python + explicit Unit(const int length, + const int mass = 0, + const int time = 0, + const int electricCurrent = 0, + const int thermodynamicTemperature = 0, + const int amountOfSubstance = 0, + const int luminousIntensity = 0, + const int angle = 0); - /** Operators. */ - //@{ - inline Unit& operator*=(const Unit& that); - inline Unit& operator/=(const Unit& that); - int operator[](int index) const; - Unit operator*(const Unit&) const; - Unit operator/(const Unit&) const; bool operator==(const Unit&) const; bool operator!=(const Unit& that) const; - Unit& operator=(const Unit&) = default; - Unit& operator=(Unit&&) = default; - Unit pow(double exp) const; - Unit sqrt() const; - Unit cbrt() const; - //@} - int length() const; - int mass() const; - int time() const; - int electricCurrent() const; - int thermodynamicTemperature() const; - int amountOfSubstance() const; - int luminousIntensity() const; - int angle() const; + Unit& operator*=(const Unit& that); + Unit& operator/=(const Unit& that); + Unit operator*(const Unit&) const; + Unit operator/(const Unit&) const; - std::string getString() const; - /// get the type as an string such as "Area", "Length" or "Pressure". - std::string getTypeString() const; + [[nodiscard]] Unit pow(const double exp) const; + [[nodiscard]] Unit root(const uint8_t num) const; - /** Predefined Unit types. */ - //@{ - static const Unit One; - /// Length unit - static const Unit Length; - /// Mass unit - static const Unit Mass; + [[nodiscard]] UnitExponents exponents() const; + [[nodiscard]] int length() const; - /// Angle + [[nodiscard]] std::string getString() const; // E.g. kg, mm^2, mm*kg/s^2 + [[nodiscard]] std::string getTypeString() const; // E.g. "Area", "Length", "Pressure" + [[nodiscard]] std::string representation() const; // E.g. "Unit: mm (1,0,0,0,0,0,0,0) [Length]" + + Unit sqrt() const + { + return root(2); + } + Unit cbrt() const + { + return root(3); + } + +private: + UnitExponents _exps {}; + std::string_view _name; + + constexpr void checkRange() + { + for (const auto exp : _exps) { + if (exp >= unitExponentLimit) { + throw OverflowError("Unit exponent overflow"); + } + if (exp < -unitExponentLimit) { + throw UnderflowError("Unit exponent underflow"); + } + } + } + + /** Returns posIndexes, negIndexes*/ + std::pair, std::vector> nonZeroValsIndexes() const; + +public: + static const Unit Acceleration; + static const Unit AmountOfSubstance; static const Unit Angle; static const Unit AngleOfFriction; - - static const Unit Density; - static const Unit Area; - static const Unit Volume; - static const Unit TimeSpan; - static const Unit Frequency; - static const Unit Velocity; - static const Unit Acceleration; - static const Unit Temperature; - + static const Unit CompressiveStrength; static const Unit CurrentDensity; + static const Unit Density; + static const Unit DissipationRate; + static const Unit DynamicViscosity; + static const Unit ElectricalCapacitance; + static const Unit ElectricalConductance; + static const Unit ElectricalConductivity; + static const Unit ElectricalInductance; + static const Unit ElectricalResistance; + static const Unit ElectricCharge; static const Unit ElectricCurrent; static const Unit ElectricPotential; - static const Unit ElectricCharge; - static const Unit SurfaceChargeDensity; - static const Unit VolumeChargeDensity; + static const Unit ElectromagneticPotential; + static const Unit Force; + static const Unit Frequency; + static const Unit HeatFlux; + static const Unit InverseArea; + static const Unit InverseLength; + static const Unit InverseVolume; + static const Unit KinematicViscosity; + static const Unit Length; + static const Unit LuminousIntensity; static const Unit MagneticFieldStrength; static const Unit MagneticFlux; static const Unit MagneticFluxDensity; static const Unit Magnetization; - static const Unit ElectricalCapacitance; - static const Unit ElectricalInductance; - static const Unit ElectricalConductance; - static const Unit ElectricalResistance; - static const Unit ElectricalConductivity; - static const Unit ElectromagneticPotential; - static const Unit AmountOfSubstance; - static const Unit LuminousIntensity; - - // Pressure - static const Unit CompressiveStrength; + static const Unit Mass; + static const Unit Moment; + static const Unit One; static const Unit Pressure; + static const Unit Power; static const Unit ShearModulus; - static const Unit Stress; - static const Unit UltimateTensileStrength; - static const Unit YieldStrength; - static const Unit YoungsModulus; - + static const Unit SpecificEnergy; + static const Unit SpecificHeat; static const Unit Stiffness; static const Unit StiffnessDensity; - - static const Unit Force; - static const Unit Work; - static const Unit Power; - static const Unit Moment; - - static const Unit SpecificEnergy; + static const Unit Stress; + static const Unit SurfaceChargeDensity; + static const Unit Temperature; + static const Unit TimeSpan; static const Unit ThermalConductivity; static const Unit ThermalExpansionCoefficient; - static const Unit VolumetricThermalExpansionCoefficient; - static const Unit SpecificHeat; static const Unit ThermalTransferCoefficient; - static const Unit HeatFlux; - static const Unit DynamicViscosity; - static const Unit KinematicViscosity; + static const Unit UltimateTensileStrength; static const Unit VacuumPermittivity; + static const Unit Velocity; + static const Unit Volume; + static const Unit VolumeChargeDensity; static const Unit VolumeFlowRate; - static const Unit DissipationRate; - - static const Unit InverseLength; - static const Unit InverseArea; - static const Unit InverseVolume; - - //@} -private: - uint32_t Val; + static const Unit VolumetricThermalExpansionCoefficient; + static const Unit Work; + static const Unit YieldStrength; + static const Unit YoungsModulus; }; -inline Unit& Unit::operator*=(const Unit& that) -{ - *this = *this * that; - return *this; -} - -inline Unit& Unit::operator/=(const Unit& that) -{ - *this = *this / that; - return *this; -} - } // namespace Base #endif // BASE_Unit_H diff --git a/src/Base/UnitPyImp.cpp b/src/Base/UnitPyImp.cpp index dd174fbf8e..35edc8450e 100644 --- a/src/Base/UnitPyImp.cpp +++ b/src/Base/UnitPyImp.cpp @@ -21,6 +21,10 @@ ***************************************************************************/ #include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif #include "Unit.h" @@ -35,25 +39,7 @@ using namespace Base; // returns a string which represents the object e.g. when printed in python std::string UnitPy::representation() const { - std::stringstream ret; - Unit* self = getUnitPtr(); - - ret << "Unit: "; - ret << self->getString() << " ("; - ret << (*self).length() << ","; - ret << (*self).mass() << ","; - ret << (*self).time() << ","; - ret << (*self).electricCurrent() << ","; - ret << (*self).thermodynamicTemperature() << ","; - ret << (*self).amountOfSubstance() << ","; - ret << (*self).luminousIntensity() << ","; - ret << (*self).angle() << ")"; - - std::string type = self->getTypeString(); - if (!type.empty()) { - ret << " [" << type << "]"; - } - return ret.str(); + return getUnitPtr()->representation(); } PyObject* UnitPy::PyMake(PyTypeObject* /*unused*/, PyObject* /*unused*/, PyObject* /*unused*/) @@ -98,14 +84,14 @@ int UnitPy::PyInit(PyObject* args, PyObject* /*kwd*/) } PyErr_Clear(); // set by PyArg_ParseTuple() - int i1 = 0; - int i2 = 0; - int i3 = 0; - int i4 = 0; - int i5 = 0; - int i6 = 0; - int i7 = 0; - int i8 = 0; + int i1 {0}; + int i2 {0}; + int i3 {0}; + int i4 {0}; + int i5 {0}; + int i6 {0}; + int i7 {0}; + int i8 {0}; if (PyArg_ParseTuple(args, "|iiiiiiii", &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) { try { *self = Unit(i1, i2, i3, i4, i5, i6, i7, i8); @@ -115,6 +101,10 @@ int UnitPy::PyInit(PyObject* args, PyObject* /*kwd*/) PyErr_SetString(PyExc_OverflowError, e.what()); return -1; } + catch (const UnderflowError& e) { + PyErr_SetString(PyExc_OverflowError, e.what()); + return -1; + } } PyErr_SetString(PyExc_TypeError, "Either string, (float,8 ints), Unit() or Quantity()"); @@ -213,13 +203,11 @@ Py::String UnitPy::getType() const Py::Tuple UnitPy::getSignature() const { - Py::Tuple tuple(8); - Unit* self = getUnitPtr(); - - for (auto i = 0; i < tuple.size(); i++) { - tuple.setItem(i, Py::Long((*self)[i])); - } - + Py::Tuple tuple {unitNumExponents}; + auto exps = getUnitPtr()->exponents(); + std::ranges::for_each(exps, [&, pos {0}](auto exp) mutable { + tuple.setItem(pos++, Py::Long {exp}); + }); return tuple; } diff --git a/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp b/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp index bf59585041..8e9ed1edc4 100644 --- a/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp +++ b/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp @@ -30,6 +30,7 @@ #include "Dialogs/DlgUnitsCalculatorImp.h" #include "ui_DlgUnitsCalculator.h" +#include #include using namespace Gui::Dialog; @@ -133,19 +134,24 @@ void DlgUnitsCalculator::textChanged(QString unit) void DlgUnitsCalculator::valueChanged(const Quantity& quant) { + std::string unitTypeStr; + try { + unitTypeStr = + Quantity::parse(ui->UnitInput->text().toStdString()).getUnit().getTypeString(); + } + catch (const Base::ParserError&) { + } // first check the unit, if it is invalid, getTypeString() outputs an empty string // explicitly check for "ee" like in "eeV" because this would trigger an exception in Unit // since it expects then a scientific notation number like "1e3" - if ((ui->UnitInput->text().mid(0, 2) == QStringLiteral("ee")) - || Unit(ui->UnitInput->text().toStdString()).getTypeString().empty()) { + if ((ui->UnitInput->text().mid(0, 2) == QStringLiteral("ee")) || unitTypeStr.empty()) { ui->ValueOutput->setText( QStringLiteral("%1 %2").arg(tr("unknown unit:"), ui->UnitInput->text())); ui->pushButton_Copy->setEnabled(false); } else { // the unit is valid // we can only convert units of the same type, thus check - if (Unit(ui->UnitInput->text().toStdString()).getTypeString() - != quant.getUnit().getTypeString()) { + if (unitTypeStr != quant.getUnit().getTypeString()) { ui->ValueOutput->setText(tr("unit mismatch")); ui->pushButton_Copy->setEnabled(false); } diff --git a/tests/src/App/ExpressionParser.cpp b/tests/src/App/ExpressionParser.cpp index d7a88267fb..fe27e45002 100644 --- a/tests/src/App/ExpressionParser.cpp +++ b/tests/src/App/ExpressionParser.cpp @@ -37,16 +37,12 @@ protected: App::DocumentObject* this_obj() { return _this_obj; } Base::Quantity parse_expression_text_as_quantity(const char* expression_text) { - auto expression = App::ExpressionParser::parse(this_obj(), expression_text); - auto expression_value = expression->getValueAsAny(); - auto quantity_result = App::any_cast(expression_value); - return quantity_result; + const auto expression = App::ExpressionParser::parse(this_obj(), expression_text); + return App::any_cast(expression->getValueAsAny()); } Base::Quantity parse_quantity_text_as_quantity(const char* quantity_text) { - auto quantity_str = std::string(quantity_text); - auto quantity_result = Base::Quantity::parse(quantity_str); - return quantity_result; + return Base::Quantity::parse(quantity_text); } private: diff --git a/tests/src/Base/Unit.cpp b/tests/src/Base/Unit.cpp index b34386d935..c4ef021f2a 100644 --- a/tests/src/Base/Unit.cpp +++ b/tests/src/Base/Unit.cpp @@ -3,191 +3,154 @@ #include // NOLINTBEGIN -TEST(Unit, TestString) +using namespace Base; + +TEST(Unit, string_simple_numerator_no_denominator) { - auto toString = [](const Base::Unit& unit) { - return unit.getString(); - }; - EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 0, 0, 0)), ""); - EXPECT_EQ(toString(Base::Unit(1, 0, 0, 0, 0, 0, 0, 0)), "mm"); - EXPECT_EQ(toString(Base::Unit(0, 1, 0, 0, 0, 0, 0, 0)), "kg"); - EXPECT_EQ(toString(Base::Unit(0, 0, 1, 0, 0, 0, 0, 0)), "s"); - EXPECT_EQ(toString(Base::Unit(0, 0, 0, 1, 0, 0, 0, 0)), "A"); - EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 1, 0, 0, 0)), "K"); - EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 1, 0, 0)), "mol"); - EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 0, 1, 0)), "cd"); - EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 0, 0, 1)), "deg"); - EXPECT_EQ(toString(Base::Unit(2, 0, 0, 0, 0, 0, 0, 0)), "mm^2"); - EXPECT_EQ(toString(Base::Unit(1, 1, -2, 0, 0, 0, 0, 0)), "mm*kg/s^2"); + EXPECT_EQ(Unit::Length.getString(), "mm"); } -TEST(Unit, TestTypeString) +TEST(Unit, string_complex_numerator_no_denominator) { - auto toString = [](const Base::Unit& unit) { - return unit.getTypeString(); - }; - EXPECT_EQ(toString(Base::Unit::Acceleration), "Acceleration"); - EXPECT_EQ(toString(Base::Unit::AmountOfSubstance), "AmountOfSubstance"); - EXPECT_EQ(toString(Base::Unit::Angle), "Angle"); - EXPECT_EQ(toString(Base::Unit::AngleOfFriction), "Angle"); // same unit as Angle - EXPECT_EQ(toString(Base::Unit::Area), "Area"); - EXPECT_EQ(toString(Base::Unit::CurrentDensity), "CurrentDensity"); - EXPECT_EQ(toString(Base::Unit::Density), "Density"); - EXPECT_EQ(toString(Base::Unit::DissipationRate), "DissipationRate"); - EXPECT_EQ(toString(Base::Unit::DynamicViscosity), "DynamicViscosity"); - EXPECT_EQ(toString(Base::Unit::ElectricalCapacitance), "ElectricalCapacitance"); - EXPECT_EQ(toString(Base::Unit::ElectricalConductance), "ElectricalConductance"); - EXPECT_EQ(toString(Base::Unit::ElectricalConductivity), "ElectricalConductivity"); - EXPECT_EQ(toString(Base::Unit::ElectricalInductance), "ElectricalInductance"); - EXPECT_EQ(toString(Base::Unit::ElectricalResistance), "ElectricalResistance"); - EXPECT_EQ(toString(Base::Unit::ElectricCharge), "ElectricCharge"); - EXPECT_EQ(toString(Base::Unit::ElectricCurrent), "ElectricCurrent"); - EXPECT_EQ(toString(Base::Unit::ElectricPotential), "ElectricPotential"); - EXPECT_EQ(toString(Base::Unit::Frequency), "Frequency"); - EXPECT_EQ(toString(Base::Unit::Force), "Force"); - EXPECT_EQ(toString(Base::Unit::HeatFlux), "HeatFlux"); - EXPECT_EQ(toString(Base::Unit::InverseArea), "InverseArea"); - EXPECT_EQ(toString(Base::Unit::InverseLength), "InverseLength"); - EXPECT_EQ(toString(Base::Unit::InverseVolume), "InverseVolume"); - EXPECT_EQ(toString(Base::Unit::KinematicViscosity), "KinematicViscosity"); - EXPECT_EQ(toString(Base::Unit::Length), "Length"); - EXPECT_EQ(toString(Base::Unit::LuminousIntensity), "LuminousIntensity"); - EXPECT_EQ(toString(Base::Unit::MagneticFieldStrength), "MagneticFieldStrength"); - EXPECT_EQ(toString(Base::Unit::MagneticFlux), "MagneticFlux"); - EXPECT_EQ(toString(Base::Unit::MagneticFluxDensity), "MagneticFluxDensity"); - EXPECT_EQ(toString(Base::Unit::Magnetization), - "MagneticFieldStrength"); // same as MagneticFieldStrength - EXPECT_EQ(toString(Base::Unit::Mass), "Mass"); - EXPECT_EQ(toString(Base::Unit::Pressure), "Pressure"); - EXPECT_EQ(toString(Base::Unit::Power), "Power"); - EXPECT_EQ(toString(Base::Unit::ShearModulus), "Pressure"); // same as Pressure - EXPECT_EQ(toString(Base::Unit::SpecificEnergy), "SpecificEnergy"); - EXPECT_EQ(toString(Base::Unit::SpecificHeat), "SpecificHeat"); - EXPECT_EQ(toString(Base::Unit::Stiffness), "Stiffness"); - EXPECT_EQ(toString(Base::Unit::Stress), "Pressure"); // same as Pressure - EXPECT_EQ(toString(Base::Unit::Temperature), "Temperature"); - EXPECT_EQ(toString(Base::Unit::ThermalConductivity), "ThermalConductivity"); - EXPECT_EQ(toString(Base::Unit::ThermalExpansionCoefficient), "ThermalExpansionCoefficient"); - EXPECT_EQ(toString(Base::Unit::ThermalTransferCoefficient), "ThermalTransferCoefficient"); - EXPECT_EQ(toString(Base::Unit::TimeSpan), "TimeSpan"); - EXPECT_EQ(toString(Base::Unit::UltimateTensileStrength), "Pressure"); // same as Pressure - EXPECT_EQ(toString(Base::Unit::VacuumPermittivity), "VacuumPermittivity"); - EXPECT_EQ(toString(Base::Unit::Velocity), "Velocity"); - EXPECT_EQ(toString(Base::Unit::Volume), "Volume"); - EXPECT_EQ(toString(Base::Unit::VolumeFlowRate), "VolumeFlowRate"); - EXPECT_EQ(toString(Base::Unit::VolumetricThermalExpansionCoefficient), - "ThermalExpansionCoefficient"); - EXPECT_EQ(toString(Base::Unit::Work), "Work"); - EXPECT_EQ(toString(Base::Unit::YieldStrength), "Pressure"); // same as Pressure - EXPECT_EQ(toString(Base::Unit::YoungsModulus), "Pressure"); // same unit as Pressure + EXPECT_EQ(Unit::Area.getString(), "mm^2"); } -TEST(Unit, strings) + +TEST(Unit, string_complex_single_denominator) { - EXPECT_STREQ(Base::Unit::Acceleration.getString().c_str(), "mm/s^2"); - EXPECT_STREQ(Base::Unit::AmountOfSubstance.getString().c_str(), "mol"); - EXPECT_STREQ(Base::Unit::Angle.getString().c_str(), "deg"); - EXPECT_STREQ(Base::Unit::AngleOfFriction.getString().c_str(), "deg"); - EXPECT_STREQ(Base::Unit::Area.getString().c_str(), "mm^2"); - EXPECT_STREQ(Base::Unit::CurrentDensity.getString().c_str(), "A/mm^2"); - EXPECT_STREQ(Base::Unit::Density.getString().c_str(), "kg/mm^3"); - EXPECT_STREQ(Base::Unit::DissipationRate.getString().c_str(), "mm^2/s^3"); + EXPECT_EQ(Unit::DissipationRate.getString(), "mm^2/s^3"); +} + +TEST(Unit, string_no_numerator) +{ + EXPECT_EQ(Unit::InverseArea.getString(), "1/mm^2"); +} + +TEST(Unit, string_complex_multi_denominator) +{ + EXPECT_EQ(Unit::MagneticFlux.getString(), "mm^2*kg/(s^2*A)"); +} + +TEST(Unit, type_string) +{ + EXPECT_EQ(Unit::MagneticFlux.getTypeString(), "MagneticFlux"); } TEST(Unit, TestEqual) { - Base::Unit unit {1}; - EXPECT_EQ(unit.pow(2) == Base::Unit {2}, true); + EXPECT_TRUE(Unit::Length == Unit::Length); } TEST(Unit, TestNotEqual) { - Base::Unit unit {1}; - EXPECT_EQ(unit.pow(2) != Base::Unit {1}, true); + EXPECT_TRUE(Unit::Length != Unit::Area); +} + +TEST(Unit, multiply_One_is_One) +{ + EXPECT_EQ(Unit::One * Unit::One, Unit::One); } TEST(Unit, TestMult) { - EXPECT_EQ(Base::Unit {} * Base::Unit {}, Base::Unit {}); - EXPECT_EQ(Base::Unit(0, 1) * Base::Unit(1, 0), Base::Unit(1, 1)); + constexpr UnitExponents arr {1, 1, 0, 0, 0, 0, 0, 0}; + EXPECT_EQ(Unit::Mass * Unit::Length, Unit {arr}); } -TEST(Unit, TestDiv) +TEST(Unit, div) { - EXPECT_EQ(Base::Unit {} * Base::Unit {}, Base::Unit {}); - EXPECT_EQ(Base::Unit(0, 1) / Base::Unit(1, 0), Base::Unit(-1, 1)); + EXPECT_EQ(Unit::Area / Unit::Length, Unit::Length); } -TEST(Unit, TestPowNoDim) +TEST(Unit, div_by_One_does_nothing) { - Base::Unit unit {}; - EXPECT_EQ(unit.pow(2), Base::Unit {0}); - EXPECT_EQ(unit == Base::Unit::One, true); + EXPECT_EQ(Unit::Area / Unit::One, Unit::Area); } -TEST(Unit, TestPowEQ1) +TEST(Unit, pow_0_is_One) { - Base::Unit unit {2}; - EXPECT_EQ(unit.pow(1), Base::Unit {2}); + EXPECT_EQ(Unit::Area.pow(0), Unit::One); } -TEST(Unit, TestPowEQ0) +TEST(Unit, pow_1_leaves_unit_unchanged) { - Base::Unit unit {2}; - EXPECT_EQ(unit.pow(0), Base::Unit {0}); + EXPECT_EQ(Unit::Area.pow(1), Unit::Area); } -TEST(Unit, TestPowGT1) +TEST(Unit, pow_2_is_squared) { - Base::Unit unit {2}; - EXPECT_EQ(unit.pow(2), Base::Unit {4}); + EXPECT_EQ(Unit::Length.pow(2), Unit::Area); } -TEST(Unit, TestPowLT1) +TEST(Unit, pow_3_is_cubed) { - Base::Unit unit {3}; - EXPECT_EQ(unit.pow(1.0 / 3.0), Base::Unit {1}); + EXPECT_EQ(Unit::Length.pow(3), Unit::Volume); } -TEST(Unit, TestPow3DIV2) +TEST(Unit, pow_less_than_one) { - Base::Unit unit {3}; - EXPECT_THROW(unit.pow(3.0 / 2.0), Base::UnitsMismatchError); + EXPECT_EQ(Unit::Volume.pow(1.0 / 3.0), Unit::Length); } -TEST(Unit, TestOverflow) +TEST(Unit, one_still_one_after_pow) { - // this tests _that_ the expected exception is thrown - EXPECT_THROW( - { - try { - Base::Unit unit {3}; - unit.pow(10000); - } - catch (const Base::OverflowError& e) { - // and this tests that it has the correct message - EXPECT_STREQ("Unit overflow in pow()", e.what()); - throw; - } - }, - Base::OverflowError); + EXPECT_EQ(Unit::One.pow(2), Unit::One); } -TEST(Unit, TestUnderflow) +TEST(Unit, square_root) { - // this tests _that_ the expected exception is thrown - EXPECT_THROW( - { - try { - Base::Unit unit {3}; - unit.pow(-10000); - } - catch (const Base::UnderflowError& e) { - // and this tests that it has the correct message - EXPECT_STREQ("Unit underflow in pow()", e.what()); - throw; - } - }, - Base::UnderflowError); + EXPECT_EQ(Unit::Area.root(2), Unit::Length); } -// NOLINTEND +TEST(Unit, cube_root) +{ + EXPECT_EQ(Unit::Volume.root(3), Unit::Length); +} + +TEST(Unit, zero_root) +{ + EXPECT_THROW([[maybe_unused]] auto res = Unit::Area.root(0), UnitsMismatchError); +} + +TEST(Unit, one_root) +{ + EXPECT_EQ(Unit::Area.root(1), Unit::Area); +} + +TEST(Unit, TestPow3div2) +{ + EXPECT_THROW([[maybe_unused]] auto res = Unit::Volume.pow(3.0 / 2.0), UnitsMismatchError); +} + +TEST(Unit, overflow) +{ + constexpr UnitExponents arr {99, 0, 0, 0, 0, 0, 0, 0}; + EXPECT_THROW([[maybe_unused]] auto res = Unit {arr}, OverflowError); +} + +TEST(Unit, underflow) +{ + constexpr UnitExponents arr {-99, 0, 0, 0, 0, 0, 0, 0}; + EXPECT_THROW([[maybe_unused]] auto res = Unit {arr}, UnderflowError); +} + +TEST(Unit, representation_simple) +{ + const std::string expect {"Unit: mm (1,0,0,0,0,0,0,0) [Length]"}; + const auto actual = Unit::Length.representation(); + EXPECT_EQ(actual, expect); +} + +TEST(Unit, representation_complex) +{ + const std::string expect {"Unit: mm^2*kg/(s^2*A) (2,1,-2,-1,0,0,0,0) [MagneticFlux]"}; + const auto actual = Unit::MagneticFlux.representation(); + EXPECT_EQ(actual, expect); +} + +TEST(Unit, representation_no_name) +{ + constexpr Unit unit {{1, 1}}; + const std::string expect {"Unit: mm*kg (1,1,0,0,0,0,0,0)"}; + const auto actual = unit.representation(); + EXPECT_EQ(actual, expect); +}