[Core] Add Boolean Functions to expressions (AND, OR, NOT) (#22506)

* [Core] Add Boolean Functions to expressions (AND, OR, BOOL, NOT)

* [Core] Add `if` function to expressions to overcome ternary operator limitations

* [Core] Update expressions grammar to recognize relational operations as arguments

* [Core] The `if` function has been removed as no consensus was reached regarding its convenience or necessity. Its inclusion was considered potentially confusing or redundant.

* [Core] Make boolean cast based on Confusion threshold.
This commit is contained in:
Frank David Martínez M
2025-08-04 22:50:13 -05:00
committed by GitHub
parent 5d43908edc
commit d4c38502d8
7 changed files with 637 additions and 420 deletions

View File

@@ -52,6 +52,7 @@
#include <Base/RotationPy.h>
#include <Base/Tools.h>
#include <Base/VectorPy.h>
#include <Base/Precision.h>
#include "ExpressionParser.h"
@@ -168,6 +169,15 @@ static inline T &&cast(App::any &&value) {
#endif
}
namespace
{
inline bool asBool(double value) {
return std::fabs(value) >= Base::Precision::Confusion();
}
}
std::string unquote(const std::string & input)
{
assert(input.size() >= 4);
@@ -1745,6 +1755,7 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f
case TANH:
case TRUNC:
case VNORMALIZE:
case NOT:
if (args.size() != 1)
ARGUMENT_THROW("exactly one required.");
break;
@@ -1810,6 +1821,8 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f
case MIN:
case STDDEV:
case SUM:
case AND:
case OR:
if (args.empty())
ARGUMENT_THROW("at least one required.");
break;
@@ -1973,6 +1986,36 @@ public:
}
};
class AndCollector : public Collector {
public:
void collect(Quantity value) override
{
if (first) {
q = Quantity(asBool(value.getValue()) ? 1 : 0);
first = false;
return;
}
if (!asBool(value.getValue())) {
q = Quantity(0);
}
}
};
class OrCollector : public Collector {
public:
void collect(Quantity value) override
{
if (first) {
q = Quantity(asBool(value.getValue()) ? 1 : 0);
first = false;
return;
}
if (asBool(value.getValue())) {
q = Quantity(1);
}
}
};
Py::Object FunctionExpression::evalAggregate(
const Expression *owner, int f, const std::vector<Expression*> &args)
{
@@ -1997,6 +2040,12 @@ Py::Object FunctionExpression::evalAggregate(
case MAX:
c = std::make_unique<MaxCollector>();
break;
case AND:
c = std::make_unique<AndCollector>();
break;
case OR:
c = std::make_unique<OrCollector>();
break;
default:
assert(false);
}
@@ -2499,6 +2548,9 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
if (v1.isDimensionlessOrUnit(Unit::Length) && v2.isDimensionlessOrUnit(Unit::Length) && v3.isDimensionlessOrUnit(Unit::Length))
break;
_EXPR_THROW("Translation units must be a length or dimensionless.", expr);
case NOT:
unit = Unit();
break;
default:
_EXPR_THROW("Unknown function: " << f,0);
}
@@ -2590,6 +2642,9 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
value)));
case TRANSLATIONM:
return translationMatrix(v1.getValue(), v2.getValue(), v3.getValue());
case NOT:
output = asBool(value) ? 0 : 1;
break;
default:
_EXPR_THROW("Unknown function: " << f,0);
}
@@ -2773,6 +2828,12 @@ void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const
ss << "stddev("; break;;
case SUM:
ss << "sum("; break;;
case AND:
ss << "and("; break;;
case OR:
ss << "or("; break;;
case NOT:
ss << "not("; break;;
default:
ss << fname << "("; break;;
}
@@ -3662,6 +3723,8 @@ static void initParser(const App::DocumentObject *owner)
registered_functions["hiddenref"] = FunctionExpression::HIDDENREF;
registered_functions["href"] = FunctionExpression::HREF;
registered_functions["not"] = FunctionExpression::NOT;
// Aggregates
registered_functions["average"] = FunctionExpression::AVERAGE;
registered_functions["count"] = FunctionExpression::COUNT;
@@ -3669,6 +3732,8 @@ static void initParser(const App::DocumentObject *owner)
registered_functions["min"] = FunctionExpression::MIN;
registered_functions["stddev"] = FunctionExpression::STDDEV;
registered_functions["sum"] = FunctionExpression::SUM;
registered_functions["and"] = FunctionExpression::AND;
registered_functions["or"] = FunctionExpression::OR;
has_registered_functions = true;
}