Add various matrix related expression functions (#8603)

Adds a few new Expression functions with the goal to:

- Simplify Placement, Rotation, Vector and Matrix object creation.
- Add new matrix functions for rotation and translation.
This commit is contained in:
Daniel-Khodabakhsh
2023-03-11 18:13:23 -08:00
committed by GitHub
parent 57aac275c7
commit 827af464e3
5 changed files with 485 additions and 204 deletions

View File

@@ -106,17 +106,19 @@ FC_LOG_LEVEL_INIT("Expression", true, true)
_e.raiseException();\
}while(0)
#define EXPR_PY_THROW(_expr) _EXPR_PY_THROW("",_expr)
#define EXPR_PY_THROW(_expr) _EXPR_PY_THROW("", _expr)
#define EXPR_THROW(_msg) _EXPR_THROW(_msg,this)
#define EXPR_THROW(_msg) _EXPR_THROW(_msg, this)
#define RUNTIME_THROW(_msg) __EXPR_THROW(Base::RuntimeError,_msg, static_cast<Expression*>(nullptr))
#define ARGUMENT_THROW(_msg) EXPR_THROW("Invalid number of arguments: " _msg)
#define TYPE_THROW(_msg) __EXPR_THROW(Base::TypeError,_msg, static_cast<Expression*>(nullptr))
#define RUNTIME_THROW(_msg) __EXPR_THROW(Base::RuntimeError, _msg, static_cast<Expression*>(nullptr))
#define PARSER_THROW(_msg) __EXPR_THROW(Base::ParserError,_msg, static_cast<Expression*>(nullptr))
#define TYPE_THROW(_msg) __EXPR_THROW(Base::TypeError, _msg, static_cast<Expression*>(nullptr))
#define PY_THROW(_msg) __EXPR_THROW(Py::RuntimeError,_msg, static_cast<Expression*>(nullptr))
#define PARSER_THROW(_msg) __EXPR_THROW(Base::ParserError, _msg, static_cast<Expression*>(nullptr))
#define PY_THROW(_msg) __EXPR_THROW(Py::RuntimeError, _msg, static_cast<Expression*>(nullptr))
static inline std::ostream &operator<<(std::ostream &os, const App::Expression *expr) {
if(expr) {
@@ -1729,60 +1731,91 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f
, args(_args)
{
switch (f) {
case ABS:
case ACOS:
case ASIN:
case ATAN:
case ABS:
case EXP:
case LOG:
case LOG10:
case SIN:
case SINH:
case TAN:
case TANH:
case SQRT:
case CBRT:
case CEIL:
case COS:
case COSH:
case ROUND:
case TRUNC:
case CEIL:
case EXP:
case FLOOR:
case MINVERT:
case STR:
case HIDDENREF:
case HREF:
case LOG:
case LOG10:
case MINVERT:
case ROTATIONX:
case ROTATIONY:
case ROTATIONZ:
case ROUND:
case SIN:
case SINH:
case SQRT:
case STR:
case TAN:
case TANH:
case TRUNC:
if (args.size() != 1)
EXPR_THROW("Invalid number of arguments: exactly one required.");
ARGUMENT_THROW("exactly one required.");
break;
case PLACEMENT:
if (args.size() > 3)
ARGUMENT_THROW("exactly one, two, or three required.");
break;
case TRANSLATIONM:
if (args.size() != 1 && args.size() != 3)
ARGUMENT_THROW("exactly one or three required.");
break;
case MOD:
case ATAN2:
case MOD:
case MROTATEX:
case MROTATEY:
case MROTATEZ:
case POW:
if (args.size() != 2)
EXPR_THROW("Invalid number of arguments: exactly two required.");
ARGUMENT_THROW("exactly two required.");
break;
case HYPOT:
case CATH:
case HYPOT:
case ROTATION:
if (args.size() < 2 || args.size() > 3)
EXPR_THROW("Invalid number of arguments: exactly two, or three required.");
ARGUMENT_THROW("exactly two, or three required.");
break;
case MTRANSLATE:
case MSCALE:
if (args.size() != 2 && args.size() != 4)
ARGUMENT_THROW("exactly two or four required.");
break;
case MROTATE:
if (args.size() < 2 || args.size() > 4)
ARGUMENT_THROW("exactly two, three, or four required.");
break;
case VECTOR:
if (args.size() != 3)
ARGUMENT_THROW("exactly three required.");
break;
case MATRIX:
if (args.size() > 16)
ARGUMENT_THROW("exactly 16 or less required.");
break;
case STDDEV:
case SUM:
case AVERAGE:
case COUNT:
case MIN:
case MAX:
case CREATE:
case MSCALE:
case MAX:
case MIN:
case STDDEV:
case SUM:
if (args.empty())
EXPR_THROW("Invalid number of arguments: at least one required.");
ARGUMENT_THROW("at least one required.");
break;
case LIST:
case TUPLE:
break;
case NONE:
case AGGREGATES:
case LAST:
case NONE:
default:
PARSER_THROW("Unknown function");
break;
@@ -1998,6 +2031,76 @@ Py::Object FunctionExpression::evalAggregate(
return pyFromQuantity(c->getQuantity());
}
Base::Vector3d FunctionExpression::evaluateSecondVectorArgument(const Expression *expression, const std::vector<Expression*> &arguments)
{
Py::Tuple vectorValues;
Py::Object secondParameter = arguments[1]->getPyValue();
if (arguments.size() == 2) {
if (!secondParameter.isSequence())
_EXPR_THROW("Second parameter is not a sequence type: '" << secondParameter.as_string() << "'.", expression);
if (PySequence_Size(secondParameter.ptr()) != 3)
_EXPR_THROW("Second parameter provided has " << PySequence_Size(secondParameter.ptr()) << " elements instead of 3.", expression);
vectorValues = Py::Tuple(Py::Sequence(secondParameter));
} else {
vectorValues = Py::Tuple(3);
vectorValues.setItem(0, secondParameter);
vectorValues.setItem(1, arguments[2]->getPyValue());
vectorValues.setItem(2, arguments[3]->getPyValue());
}
Vector3d vector;
if (!PyArg_ParseTuple(vectorValues.ptr(), "ddd", &vector.x, &vector.y, &vector.z)) {
PyErr_Clear();
_EXPR_THROW("Error parsing scale values.", expression);
}
return vector;
}
void FunctionExpression::initialiseObject(const Py::Object *object, const std::vector<Expression*> &arguments, const unsigned long offset)
{
if (arguments.size() > offset) {
Py::Tuple constructorArguments(arguments.size() - offset);
for (unsigned i = offset; i < arguments.size(); ++i)
constructorArguments.setItem(i - offset, arguments[i]->getPyValue());
Py::Dict kwd;
PyObjectBase::__PyInit(object->ptr(), constructorArguments.ptr(), kwd.ptr());
}
}
Py::Object FunctionExpression::transformFirstArgument(
const Expression* expression,
const std::vector<Expression*> &arguments,
const Base::Matrix4D* transformationMatrix
)
{
Py::Object target = arguments[0]->getPyValue();
if (PyObject_TypeCheck(target.ptr(), &Base::MatrixPy::Type)) {
Base::Matrix4D matrix = static_cast<Base::MatrixPy*>(target.ptr())->value();
return Py::asObject(new Base::MatrixPy(*transformationMatrix * matrix));
} else if (PyObject_TypeCheck(target.ptr(), &Base::PlacementPy::Type)) {
Base::Matrix4D placementMatrix =
static_cast<Base::PlacementPy*>(target.ptr())->getPlacementPtr()->toMatrix();
return Py::asObject(new Base::PlacementPy(Base::Placement(*transformationMatrix * placementMatrix)));
} else if (PyObject_TypeCheck(target.ptr(), &Base::RotationPy::Type)) {
Base::Matrix4D rotatioMatrix;
static_cast<Base::RotationPy*>(target.ptr())->getRotationPtr()->getValue(rotatioMatrix);
return Py::asObject(new Base::RotationPy(Base::Rotation(*transformationMatrix * rotatioMatrix)));
}
_EXPR_THROW("Function requires the first argument to be either Matrix, Placement or Rotation.", expression);
}
Py::Object FunctionExpression::translationMatrix(double x, double y, double z)
{
Base::Matrix4D matrix;
matrix.move(x, y, z);
return Py::asObject(new Base::MatrixPy(matrix));
}
Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std::vector<Expression*> &args)
{
if(!expr || !expr->getOwner())
@@ -2007,103 +2110,163 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
if (f > AGGREGATES)
return evalAggregate(expr, f, args);
if(f == LIST) {
if(args.size() == 1 && args[0]->isDerivedFrom(RangeExpression::getClassTypeId()))
switch (f) {
case LIST: {
if (args.size() == 1 && args[0]->isDerivedFrom(RangeExpression::getClassTypeId()))
return args[0]->getPyValue();
Py::List list(args.size());
int i=0;
for(auto &arg : args)
list.setItem(i++,arg->getPyValue());
int i = 0;
for (auto &arg : args)
list.setItem(i++, arg->getPyValue());
return list;
} else if (f == TUPLE) {
if(args.size() == 1 && args[0]->isDerivedFrom(RangeExpression::getClassTypeId()))
}
case TUPLE: {
if (args.size() == 1 && args[0]->isDerivedFrom(RangeExpression::getClassTypeId()))
return Py::Tuple(args[0]->getPyValue());
Py::Tuple tuple(args.size());
int i=0;
for(auto &arg : args)
tuple.setItem(i++,arg->getPyValue());
int i = 0;
for (auto &arg : args)
tuple.setItem(i++, arg->getPyValue());
return tuple;
} else if (f == MSCALE) {
if(args.size() < 2)
_EXPR_THROW("Function requires at least two arguments.",expr);
Py::Object pymat = args[0]->getPyValue();
Py::Object pyscale;
if(PyObject_TypeCheck(pymat.ptr(),&Base::MatrixPy::Type)) {
if(args.size() == 2) {
Py::Object obj = args[1]->getPyValue();
if(obj.isSequence() && PySequence_Size(obj.ptr())==3)
pyscale = Py::Tuple(Py::Sequence(obj));
} else if(args.size() == 4) {
Py::Tuple tuple(3);
tuple.setItem(0,args[1]->getPyValue());
tuple.setItem(1,args[2]->getPyValue());
tuple.setItem(2,args[3]->getPyValue());
pyscale = tuple;
}
}
if(!pyscale.isNone()) {
Base::Vector3d vec;
if (!PyArg_ParseTuple(pyscale.ptr(), "ddd", &vec.x,&vec.y,&vec.z))
PyErr_Clear();
else {
auto mat = static_cast<Base::MatrixPy*>(pymat.ptr())->value();
mat.scale(vec);
return Py::asObject(new Base::MatrixPy(mat));
}
}
_EXPR_THROW("Function requires arguments to be either "
"(matrix,vector) or (matrix,number,number,number).", expr);
}
}
if(args.empty())
_EXPR_THROW("Function requires at least one argument.",expr);
if (f == MINVERT) {
switch (f) {
case MINVERT: {
Py::Object pyobj = args[0]->getPyValue();
if (PyObject_TypeCheck(pyobj.ptr(),&Base::MatrixPy::Type)) {
if (PyObject_TypeCheck(pyobj.ptr(), &Base::MatrixPy::Type)) {
auto m = static_cast<Base::MatrixPy*>(pyobj.ptr())->value();
if (fabs(m.determinant()) <= DBL_EPSILON)
_EXPR_THROW("Cannot invert singular matrix.",expr);
_EXPR_THROW("Cannot invert singular matrix.", expr);
m.inverseGauss();
return Py::asObject(new Base::MatrixPy(m));
} else if (PyObject_TypeCheck(pyobj.ptr(),&Base::PlacementPy::Type)) {
} else if (PyObject_TypeCheck(pyobj.ptr(), &Base::PlacementPy::Type)) {
const auto &pla = *static_cast<Base::PlacementPy*>(pyobj.ptr())->getPlacementPtr();
return Py::asObject(new Base::PlacementPy(pla.inverse()));
} else if (PyObject_TypeCheck(pyobj.ptr(),&Base::RotationPy::Type)) {
} else if (PyObject_TypeCheck(pyobj.ptr(), &Base::RotationPy::Type)) {
const auto &rot = *static_cast<Base::RotationPy*>(pyobj.ptr())->getRotationPtr();
return Py::asObject(new Base::RotationPy(rot.inverse()));
}
_EXPR_THROW("Function requires the first argument to be either Matrix, Placement or Rotation.",expr);
_EXPR_THROW(
"Function requires the first argument to be either Matrix, Placement or Rotation.",
expr);
break;
}
case MROTATE: {
Py::Object rotationObject = args[1]->getPyValue();
if (!PyObject_TypeCheck(rotationObject.ptr(), &Base::RotationPy::Type))
{
rotationObject = Py::asObject(new Base::RotationPy(Base::Rotation()));
initialiseObject(&rotationObject, args, 1);
}
} else if (f == CREATE) {
Base::Matrix4D rotationMatrix;
static_cast<Base::RotationPy*>(rotationObject.ptr())->getRotationPtr()->getValue(rotationMatrix);
return transformFirstArgument(expr, args, &rotationMatrix);
}
case MROTATEX:
case MROTATEY:
case MROTATEZ:
{
Py::Object rotationAngleParameter = args[1]->getPyValue();
Quantity rotationAngle = pyToQuantity(rotationAngleParameter, expr, "Invalid rotation angle.");
if (!(rotationAngle.isDimensionlessOrUnit(Unit::Angle)))
_EXPR_THROW("Unit must be either empty or an angle.", expr);
Rotation rotation = Base::Rotation(
Vector3d(static_cast<double>(f == MROTATEX), static_cast<double>(f == MROTATEY), static_cast<double>(f == MROTATEZ)),
rotationAngle.getValue() * M_PI / 180.0);
Base::Matrix4D rotationMatrix;
rotation.getValue(rotationMatrix);
return transformFirstArgument(expr, args, &rotationMatrix);
}
case MSCALE: {
Vector3d scaleValues = evaluateSecondVectorArgument(expr, args);
Base::Matrix4D scaleMatrix;
scaleMatrix.scale(scaleValues);
return transformFirstArgument(expr, args, &scaleMatrix);
}
case MTRANSLATE: {
Vector3d translateValues = evaluateSecondVectorArgument(expr, args);
Base::Matrix4D translateMatrix;
translateMatrix.move(translateValues);
Py::Object target = args[0]->getPyValue();
if (PyObject_TypeCheck(target.ptr(), &Base::RotationPy::Type)) {
Base::Matrix4D targetRotatioMatrix;
static_cast<Base::RotationPy*>(target.ptr())->getRotationPtr()->getValue(targetRotatioMatrix);
return Py::asObject(new Base::PlacementPy(Base::Placement(translateMatrix * targetRotatioMatrix)));
}
return transformFirstArgument(expr, args, &translateMatrix);
}
case CREATE: {
Py::Object pytype = args[0]->getPyValue();
if(!pytype.isString())
_EXPR_THROW("Function requires the first argument to be a string.",expr);
if (!pytype.isString())
_EXPR_THROW("Function requires the first argument to be a string.", expr);
std::string type(pytype.as_string());
Py::Object res;
if(boost::iequals(type,"matrix"))
if (boost::iequals(type, "matrix"))
res = Py::asObject(new Base::MatrixPy(Base::Matrix4D()));
else if(boost::iequals(type,"vector"))
else if (boost::iequals(type, "vector"))
res = Py::asObject(new Base::VectorPy(Base::Vector3d()));
else if(boost::iequals(type,"placement"))
else if (boost::iequals(type, "placement"))
res = Py::asObject(new Base::PlacementPy(Base::Placement()));
else if(boost::iequals(type,"rotation"))
else if (boost::iequals(type, "rotation"))
res = Py::asObject(new Base::RotationPy(Base::Rotation()));
else
_EXPR_THROW("Unknown type '" << type << "'.",expr);
if(args.size()>1) {
Py::Tuple tuple(args.size()-1);
for(unsigned i=1;i<args.size();++i)
tuple.setItem(i-1,args[i]->getPyValue());
Py::Dict dict;
PyObjectBase::__PyInit(res.ptr(),tuple.ptr(),dict.ptr());
}
_EXPR_THROW("Unknown type '" << type << "'.", expr);
initialiseObject(&res, args, 1);
return res;
} else if (f == STR) {
}
case MATRIX: {
Py::Object matrix = Py::asObject(new Base::MatrixPy(Base::Matrix4D()));
initialiseObject(&matrix, args);
return matrix;
}
case PLACEMENT: {
Py::Object placement = Py::asObject(new Base::PlacementPy(Base::Placement()));
initialiseObject(&placement, args);
return placement;
}
case ROTATION: {
Py::Object rotation = Py::asObject(new Base::RotationPy(Base::Rotation()));
initialiseObject(&rotation, args);
return rotation;
}
case STR:
return Py::String(args[0]->getPyValue().as_string());
} else if (f == HIDDENREF || f == HREF) {
case TRANSLATIONM: {
if (args.size() != 1)
break; // Break and proceed to 3 size version.
Py::Object parameter = args[0]->getPyValue();
if (!parameter.isSequence())
_EXPR_THROW("Not sequence type: '" << parameter.as_string() << "'.", expr);
if (PySequence_Size(parameter.ptr()) != 3)
_EXPR_THROW("Sequence provided has " << PySequence_Size(parameter.ptr()) << " elements instead of 3.", expr);
double x, y, z;
if (!PyArg_ParseTuple(Py::Tuple(Py::Sequence(parameter)).ptr(), "ddd", &x, &y, &z)) {
PyErr_Clear();
_EXPR_THROW("Error parsing sequence.", expr);
}
return translationMatrix(x, y, z);
}
case VECTOR: {
Py::Object vector = Py::asObject(new Base::VectorPy(Base::Vector3d()));
initialiseObject(&vector, args);
return vector;
}
case HIDDENREF:
case HREF:
return args[0]->getPyValue();
}
@@ -2111,13 +2274,13 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
Quantity v1 = pyToQuantity(e1,expr,"Invalid first argument.");
Py::Object e2;
Quantity v2;
if(args.size()>1) {
if (args.size() > 1) {
e2 = args[1]->getPyValue();
v2 = pyToQuantity(e2,expr,"Invalid second argument.");
}
Py::Object e3;
Quantity v3;
if(args.size()>2) {
if (args.size() > 2) {
e3 = args[2]->getPyValue();
v3 = pyToQuantity(e3,expr,"Invalid third argument.");
}
@@ -2133,8 +2296,11 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
case COS:
case SIN:
case TAN:
if (!(v1.getUnit() == Unit::Angle || v1.getUnit().isEmpty()))
_EXPR_THROW("Unit must be either empty or an angle.",expr);
case ROTATIONX:
case ROTATIONY:
case ROTATIONZ:
if (!(v1.isDimensionlessOrUnit(Unit::Angle)))
_EXPR_THROW("Unit must be either empty or an angle.", expr);
// Convert value to radians
value *= M_PI / 180.0;
@@ -2143,8 +2309,8 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
case ACOS:
case ASIN:
case ATAN:
if (!v1.getUnit().isEmpty())
_EXPR_THROW("Unit must be empty.",expr);
if (!v1.isDimensionless())
_EXPR_THROW("Unit must be empty.", expr);
unit = Unit::Angle;
scaler = 180.0 / M_PI;
break;
@@ -2154,7 +2320,7 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
case SINH:
case TANH:
case COSH:
if (!v1.getUnit().isEmpty())
if (!v1.isDimensionless())
_EXPR_THROW("Unit must be empty.",expr);
unit = Unit();
break;
@@ -2233,12 +2399,12 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
if (e2.isNone())
_EXPR_THROW("Invalid second argument.",expr);
if (!v2.getUnit().isEmpty())
if (!v2.isDimensionless())
_EXPR_THROW("Exponent is not allowed to have a unit.",expr);
// Compute new unit for exponentiation
double exponent = v2.getValue();
if (!v1.getUnit().isEmpty()) {
if (!v1.isDimensionless()) {
if (exponent - boost::math::round(exponent) < 1e-9)
unit = v1.getUnit().pow(exponent);
else
@@ -2261,6 +2427,10 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
}
unit = v1.getUnit();
break;
case TRANSLATIONM:
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);
default:
_EXPR_THROW("Unknown function: " << f,0);
}
@@ -2344,6 +2514,14 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
case FLOOR:
output = floor(value);
break;
case ROTATIONX:
case ROTATIONY:
case ROTATIONZ:
return Py::asObject(new Base::RotationPy(Base::Rotation(
Vector3d(static_cast<double>(f == ROTATIONX), static_cast<double>(f == ROTATIONY), static_cast<double>(f == ROTATIONZ)),
value)));
case TRANSLATIONM:
return translationMatrix(v1.getValue(), v2.getValue(), v3.getValue());
default:
_EXPR_THROW("Unknown function: " << f,0);
}
@@ -2397,82 +2575,108 @@ Expression *FunctionExpression::simplify() const
void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const
{
switch (f) {
case ABS:
ss << "abs("; break;;
case ACOS:
ss << "acos("; break;;
case ASIN:
ss << "asin("; break;;
case ATAN:
ss << "atan("; break;;
case ABS:
ss << "abs("; break;;
case EXP:
ss << "exp("; break;;
case LOG:
ss << "log("; break;;
case LOG10:
ss << "log10("; break;;
case SIN:
ss << "sin("; break;;
case SINH:
ss << "sinh("; break;;
case TAN:
ss << "tan("; break;;
case TANH:
ss << "tanh("; break;;
case SQRT:
ss << "sqrt("; break;;
case ATAN2:
ss << "atan2("; break;;
case CATH:
ss << "cath("; break;;
case CBRT:
ss << "cbrt("; break;;
case CEIL:
ss << "ceil("; break;;
case COS:
ss << "cos("; break;;
case COSH:
ss << "cosh("; break;;
case MOD:
ss << "mod("; break;;
case ATAN2:
ss << "atan2("; break;;
case POW:
ss << "pow("; break;;
case HYPOT:
ss << "hypot("; break;;
case CATH:
ss << "cath("; break;;
case ROUND:
ss << "round("; break;;
case TRUNC:
ss << "trunc("; break;;
case CEIL:
ss << "ceil("; break;;
case EXP:
ss << "exp("; break;;
case FLOOR:
ss << "floor("; break;;
case SUM:
ss << "sum("; break;;
case COUNT:
ss << "count("; break;;
case AVERAGE:
ss << "average("; break;;
case STDDEV:
ss << "stddev("; break;;
case MIN:
ss << "min("; break;;
case MAX:
ss << "max("; break;;
case LIST:
ss << "list("; break;;
case TUPLE:
ss << "tuple("; break;;
case MSCALE:
ss << "mscale("; break;;
case HYPOT:
ss << "hypot("; break;;
case LOG:
ss << "log("; break;;
case LOG10:
ss << "log10("; break;;
case MOD:
ss << "mod("; break;;
case POW:
ss << "pow("; break;;
case ROUND:
ss << "round("; break;;
case SIN:
ss << "sin("; break;;
case SINH:
ss << "sinh("; break;;
case SQRT:
ss << "sqrt("; break;;
case TAN:
ss << "tan("; break;;
case TANH:
ss << "tanh("; break;;
case TRUNC:
ss << "trunc("; break;;
case MINVERT:
ss << "minvert("; break;;
case MROTATE:
ss << "mrotate("; break;;
case MROTATEX:
ss << "mrotatex("; break;;
case MROTATEY:
ss << "mrotatey("; break;;
case MROTATEZ:
ss << "mrotatez("; break;;
case MSCALE:
ss << "mscale("; break;;
case MTRANSLATE:
ss << "mtranslate("; break;;
case CREATE:
ss << "create("; break;;
case LIST:
ss << "list("; break;;
case MATRIX:
ss << "matrix("; break;;
case PLACEMENT:
ss << "placement("; break;;
case ROTATION:
ss << "rotation("; break;;
case ROTATIONX:
ss << "rotationx("; break;;
case ROTATIONY:
ss << "rotationy("; break;;
case ROTATIONZ:
ss << "rotationz("; break;;
case STR:
ss << "str("; break;;
case TRANSLATIONM:
ss << "translationm("; break;;
case TUPLE:
ss << "tuple("; break;;
case VECTOR:
ss << "vector("; break;;
case HIDDENREF:
ss << "hiddenref("; break;;
case HREF:
ss << "href("; break;;
case AVERAGE:
ss << "average("; break;;
case COUNT:
ss << "count("; break;;
case MAX:
ss << "max("; break;;
case MIN:
ss << "min("; break;;
case STDDEV:
ss << "stddev("; break;;
case SUM:
ss << "sum("; break;;
default:
ss << fname << "("; break;;
}
@@ -3267,46 +3471,62 @@ static void initParser(const App::DocumentObject *owner)
unitExpression = valueExpression = false;
if (!has_registered_functions) {
registered_functions["abs"] = FunctionExpression::ABS;
registered_functions["acos"] = FunctionExpression::ACOS;
registered_functions["asin"] = FunctionExpression::ASIN;
registered_functions["atan"] = FunctionExpression::ATAN;
registered_functions["abs"] = FunctionExpression::ABS;
registered_functions["exp"] = FunctionExpression::EXP;
registered_functions["log"] = FunctionExpression::LOG;
registered_functions["log10"] = FunctionExpression::LOG10;
registered_functions["sin"] = FunctionExpression::SIN;
registered_functions["sinh"] = FunctionExpression::SINH;
registered_functions["tan"] = FunctionExpression::TAN;
registered_functions["tanh"] = FunctionExpression::TANH;
registered_functions["sqrt"] = FunctionExpression::SQRT;
registered_functions["atan2"] = FunctionExpression::ATAN2;
registered_functions["cath"] = FunctionExpression::CATH;
registered_functions["cbrt"] = FunctionExpression::CBRT;
registered_functions["ceil"] = FunctionExpression::CEIL;
registered_functions["cos"] = FunctionExpression::COS;
registered_functions["cosh"] = FunctionExpression::COSH;
registered_functions["atan2"] = FunctionExpression::ATAN2;
registered_functions["exp"] = FunctionExpression::EXP;
registered_functions["floor"] = FunctionExpression::FLOOR;
registered_functions["hypot"] = FunctionExpression::HYPOT;
registered_functions["log"] = FunctionExpression::LOG;
registered_functions["log10"] = FunctionExpression::LOG10;
registered_functions["mod"] = FunctionExpression::MOD;
registered_functions["pow"] = FunctionExpression::POW;
registered_functions["round"] = FunctionExpression::ROUND;
registered_functions["sin"] = FunctionExpression::SIN;
registered_functions["sinh"] = FunctionExpression::SINH;
registered_functions["sqrt"] = FunctionExpression::SQRT;
registered_functions["tan"] = FunctionExpression::TAN;
registered_functions["tanh"] = FunctionExpression::TANH;
registered_functions["trunc"] = FunctionExpression::TRUNC;
registered_functions["ceil"] = FunctionExpression::CEIL;
registered_functions["floor"] = FunctionExpression::FLOOR;
registered_functions["hypot"] = FunctionExpression::HYPOT;
registered_functions["cath"] = FunctionExpression::CATH;
registered_functions["list"] = FunctionExpression::LIST;
registered_functions["tuple"] = FunctionExpression::TUPLE;
registered_functions["mscale"] = FunctionExpression::MSCALE;
registered_functions["minvert"] = FunctionExpression::MINVERT;
registered_functions["mrotate"] = FunctionExpression::MROTATE;
registered_functions["mrotatex"] = FunctionExpression::MROTATEX;
registered_functions["mrotatey"] = FunctionExpression::MROTATEY;
registered_functions["mrotatez"] = FunctionExpression::MROTATEZ;
registered_functions["mscale"] = FunctionExpression::MSCALE;
registered_functions["mtranslate"] = FunctionExpression::MTRANSLATE;
registered_functions["create"] = FunctionExpression::CREATE;
registered_functions["list"] = FunctionExpression::LIST;
registered_functions["matrix"] = FunctionExpression::MATRIX;
registered_functions["placement"] = FunctionExpression::PLACEMENT;
registered_functions["rotation"] = FunctionExpression::ROTATION;
registered_functions["rotationx"] = FunctionExpression::ROTATIONX;
registered_functions["rotationy"] = FunctionExpression::ROTATIONY;
registered_functions["rotationz"] = FunctionExpression::ROTATIONZ;
registered_functions["str"] = FunctionExpression::STR;
registered_functions["translationm"] = FunctionExpression::TRANSLATIONM;
registered_functions["tuple"] = FunctionExpression::TUPLE;
registered_functions["vector"] = FunctionExpression::VECTOR;
registered_functions["hiddenref"] = FunctionExpression::HIDDENREF;
registered_functions["href"] = FunctionExpression::HREF;
// Aggregates
registered_functions["sum"] = FunctionExpression::SUM;
registered_functions["count"] = FunctionExpression::COUNT;
registered_functions["average"] = FunctionExpression::AVERAGE;
registered_functions["stddev"] = FunctionExpression::STDDEV;
registered_functions["min"] = FunctionExpression::MIN;
registered_functions["count"] = FunctionExpression::COUNT;
registered_functions["max"] = FunctionExpression::MAX;
registered_functions["min"] = FunctionExpression::MIN;
registered_functions["stddev"] = FunctionExpression::STDDEV;
registered_functions["sum"] = FunctionExpression::SUM;
has_registered_functions = true;
}

View File

@@ -26,7 +26,9 @@
#define EXPRESSION_PARSER_H
#include "Expression.h"
#include <Base/Matrix.h>
#include <Base/Quantity.h>
#include <Base/Vector3D.h>
namespace App {
@@ -244,48 +246,66 @@ public:
NONE,
// Normal functions taking one or two arguments
ABS,
ACOS,
ASIN,
ATAN,
ABS,
EXP,
LOG,
LOG10,
SIN,
SINH,
TAN,
TANH,
SQRT,
ATAN2,
CATH,
CBRT,
CEIL,
COS,
COSH,
ATAN2,
EXP,
FLOOR,
HYPOT,
LOG,
LOG10,
MOD,
POW,
ROUND,
SIN,
SINH,
SQRT,
TAN,
TANH,
TRUNC,
CEIL,
FLOOR,
HYPOT,
CATH,
LIST,
TUPLE,
MSCALE, // matrix scale by vector
// Matrix
MINVERT, // invert matrix/placement/rotation
CREATE, // create new object of a given type
MROTATE, // Rotate matrix/placement/rotation around axis, by rotation object, or by euler angles.
MROTATEX, // Rotate matrix/placement/rotation around x-axis.
MROTATEY, // Rotate matrix/placement/rotation around y-axis.
MROTATEZ, // Rotate matrix/placement/rotation around z-axis.
MSCALE, // matrix scale by vector
MTRANSLATE, // Translate matrix/placement.
// Object creation
CREATE, // Create new object of a given type.
LIST, // Create Python list.
MATRIX, // Create matrix object.
PLACEMENT, // Create placement object.
ROTATION, // Create rotation object.
ROTATIONX, // Create x-axis rotation object.
ROTATIONY, // Create y-axis rotation object.
ROTATIONZ, // Create z-axis rotation object.
STR, // stringify
TRANSLATIONM, // Create translation matrix object.
TUPLE, // Create Python tuple.
VECTOR, // Create vector object.
HIDDENREF, // hidden reference that has no dependency check
HREF, // deprecated alias of HIDDENREF
// Aggregates
AGGREGATES,
SUM,
AVERAGE,
STDDEV,
COUNT,
MIN,
MAX,
MIN,
STDDEV,
SUM,
// Last one
LAST,
@@ -307,6 +327,13 @@ public:
protected:
static Py::Object evalAggregate(const Expression *owner, int type, const std::vector<Expression*> &args);
static Base::Vector3d evaluateSecondVectorArgument(const Expression *expression, const std::vector<Expression*> &arguments);
static void initialiseObject(const Py::Object *object, const std::vector<Expression*> &arguments, const unsigned long offset = 0);
static Py::Object transformFirstArgument(
const Expression *expression,
const std::vector<Expression*> &arguments,
const Base::Matrix4D *transformationMatrix);
static Py::Object translationMatrix(double x, double y, double z);
Py::Object _getPyValue() const override;
Expression * _copy() const override;
void _visit(ExpressionVisitor & v) override;

View File

@@ -271,6 +271,12 @@ bool Quantity::isDimensionless() const
return isValid() && myUnit.isEmpty();
}
/// true if it has a specific unit or no dimension.
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
{

View File

@@ -181,6 +181,8 @@ public:
/// true if it has a number without a 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

View File

@@ -894,7 +894,7 @@ class SpreadsheetCases(unittest.TestCase):
pla = FreeCAD.Placement(vec,rot)
ipla = pla.inverse()
sheet.set('A1', '=create(<<vector>>, 2, 1, 2)')
sheet.set('A1', '=vector(2, 1, 2)')
# different ways of calling mscale()
sheet.set('B1', '=mscale(create(<<matrix>>), A1)')
@@ -907,10 +907,10 @@ class SpreadsheetCases(unittest.TestCase):
sheet.set('D2', '=A2^0')
sheet.set('E2', '=A2^1')
sheet.set('F2', '=A2^2')
sheet.set('G2', '=create(<<matrix>>, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)')
sheet.set('G2', '=matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)')
sheet.set('H2', '=G2^-1')
sheet.set('A3', '=create(<<rotation>>, create(<<vector>>, 0, 1, 0), 45)')
sheet.set('A3', '=rotation(vector(0, 1, 0), 45)')
# test rotation power operation
sheet.set('B3', '=A3^-2')
@@ -919,7 +919,7 @@ class SpreadsheetCases(unittest.TestCase):
sheet.set('E3', '=A3^1')
sheet.set('F3', '=A3^2')
sheet.set('A4', '=create(<<placement>>, A1, A3)')
sheet.set('A4', '=placement(A1, A3)')
# test placement power operation
sheet.set('B4', '=A4^-2')
@@ -944,6 +944,20 @@ class SpreadsheetCases(unittest.TestCase):
sheet.set('E6', '=(E2 * E3)^-1 * E4^-1 * E5')
sheet.set('F6', '=(F3*F4*F2)^-1 * F5')
# Rotate and translate.
sheet.set('A7', '=placement(vector(1; 2; 3), vector(1; 0; 0); 0)')
sheet.set('B7', '=mrotate(A7; vector(1; 0; 0); 90)')
sheet.set('C7', '=mrotatex(A7; 90)')
sheet.set('D7', '=mrotatey(A7; 90)')
sheet.set('E7', '=mrotatez(A7; 90)')
sheet.set('F7', '=mtranslate(A7; vector(1; 2; 3))')
sheet.set('G7', '=mtranslate(A7; 1; 2; 3)')
# Compatibility with old syntax.
sheet.set('A8', '=create(<<vector>>, 2, 1, 2)')
sheet.set('B8', '=create(<<rotation>>, create(<<vector>>, 0, 1, 0), 45)')
sheet.set('C8', '=create(<<placement>>, A8, B8)')
self.doc.recompute()
self.assertEqual(sheet.A1,vec)
@@ -1014,6 +1028,18 @@ class SpreadsheetCases(unittest.TestCase):
self.assertLess(sheet.E6.distanceToPoint(vec),tol)
self.assertLess(sheet.F6.distanceToPoint(vec),tol)
self.assertTrue(sheet.A7.Base.isEqual(FreeCAD.Vector(1, 2, 3), tol))
self.assertTrue(sheet.B7.Base.isEqual(FreeCAD.Vector(1, -3, 2), tol))
self.assertTrue(sheet.C7.Base.isEqual(FreeCAD.Vector(1, -3, 2), tol))
self.assertTrue(sheet.D7.Base.isEqual(FreeCAD.Vector(3, 2.0, -1), tol))
self.assertTrue(sheet.E7.Base.isEqual(FreeCAD.Vector(-2, 1, 3.0), tol))
self.assertTrue(sheet.F7.Base.isEqual(FreeCAD.Vector(2, 4, 6), tol))
self.assertTrue(sheet.G7.Base.isEqual(FreeCAD.Vector(2, 4, 6), tol))
self.assertEqual(sheet.A8, vec)
self.assertEqual(sheet.B8, rot)
self.assertEqual(sheet.C8, pla)
def testIssue3128(self):
""" Regression test for issue 3128; mod should work with arbitrary units for both arguments """
sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet')