Expressions: Add Vector API expression functions (#10237)

This commit is contained in:
Daniel-Khodabakhsh
2023-08-15 21:00:16 -07:00
committed by GitHub
parent a49e104993
commit 8003606222
3 changed files with 220 additions and 0 deletions

View File

@@ -1757,6 +1757,7 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f
case TAN:
case TANH:
case TRUNC:
case VNORMALIZE:
if (args.size() != 1)
ARGUMENT_THROW("exactly one required.");
break;
@@ -1774,6 +1775,12 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f
case MROTATEY:
case MROTATEZ:
case POW:
case VANGLE:
case VCROSS:
case VDOT:
case VSCALEX:
case VSCALEY:
case VSCALEZ:
if (args.size() != 2)
ARGUMENT_THROW("exactly two required.");
break;
@@ -1793,9 +1800,18 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f
ARGUMENT_THROW("exactly two, three, or four required.");
break;
case VECTOR:
case VLINEDIST:
case VLINESEGDIST:
case VLINEPROJ:
case VPLANEDIST:
case VPLANEPROJ:
if (args.size() != 3)
ARGUMENT_THROW("exactly three required.");
break;
case VSCALE:
if (args.size() != 4)
ARGUMENT_THROW("exactly four required.");
break;
case MATRIX:
if (args.size() > 16)
ARGUMENT_THROW("exactly 16 or less required.");
@@ -2101,6 +2117,36 @@ Py::Object FunctionExpression::translationMatrix(double x, double y, double z)
return Py::asObject(new Base::MatrixPy(matrix));
}
double FunctionExpression::extractLengthValueArgument(
const Expression *expression,
const std::vector<Expression*> &arguments,
int argumentIndex
)
{
Quantity argumentQuantity = pyToQuantity(arguments[argumentIndex]->getPyValue(), expression);
if (!(argumentQuantity.isDimensionlessOrUnit(Unit::Length))) {
_EXPR_THROW("Unit must be either empty or a length.", expression);
}
return argumentQuantity.getValue();
}
Base::Vector3d FunctionExpression::extractVectorArgument(
const Expression *expression,
const std::vector<Expression*> &arguments,
int argumentIndex
)
{
Py::Object argument = arguments[argumentIndex]->getPyValue();
if (!PyObject_TypeCheck(argument.ptr(), &Base::VectorPy::Type)) {
_EXPR_THROW("Argument must be a vector.", expression);
}
return static_cast<Base::VectorPy*>(argument.ptr())->value();
}
Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std::vector<Expression*> &args)
{
if(!expr || !expr->getOwner())
@@ -2268,6 +2314,76 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std
case HIDDENREF:
case HREF:
return args[0]->getPyValue();
case VANGLE:
case VCROSS:
case VDOT:
case VLINEDIST:
case VLINESEGDIST:
case VLINEPROJ:
case VNORMALIZE:
case VPLANEDIST:
case VPLANEPROJ:
case VSCALE:
case VSCALEX:
case VSCALEY:
case VSCALEZ: {
Base::Vector3d vector1 = extractVectorArgument(expr, args, 0);
switch (f) {
case VNORMALIZE:
return Py::asObject(new Base::VectorPy(vector1.Normalize()));
case VSCALE: {
double scaleX = extractLengthValueArgument(expr, args, 1);
double scaleY = extractLengthValueArgument(expr, args, 2);
double scaleZ = extractLengthValueArgument(expr, args, 3);
vector1.Scale(scaleX, scaleY, scaleZ);
return Py::asObject(new Base::VectorPy(vector1));
}
case VSCALEX: {
double scaleX = extractLengthValueArgument(expr, args, 1);
vector1.ScaleX(scaleX);
return Py::asObject(new Base::VectorPy(vector1));
}
case VSCALEY: {
double scaleY = extractLengthValueArgument(expr, args, 1);
vector1.ScaleY(scaleY);
return Py::asObject(new Base::VectorPy(vector1));
}
case VSCALEZ: {
double scaleZ = extractLengthValueArgument(expr, args, 1);
vector1.ScaleZ(scaleZ);
return Py::asObject(new Base::VectorPy(vector1));
}
}
Base::Vector3d vector2 = extractVectorArgument(expr, args, 1);
switch (f) {
case VANGLE:
return Py::asObject(new QuantityPy(new Quantity(vector1.GetAngle(vector2) * 180 / M_PI, Unit::Angle)));
case VCROSS:
return Py::asObject(new Base::VectorPy(vector1.Cross(vector2)));
case VDOT:
return Py::Float(vector1.Dot(vector2));
}
Base::Vector3d vector3 = extractVectorArgument(expr, args, 2);
switch (f) {
case VLINEDIST:
return Py::asObject(new QuantityPy(new Quantity(vector1.DistanceToLine(vector2, vector3), Unit::Length)));
case VLINESEGDIST:
return Py::asObject(new Base::VectorPy(vector1.DistanceToLineSegment(vector2, vector3)));
case VLINEPROJ:
vector1.ProjectToLine(vector2, vector3);
return Py::asObject(new Base::VectorPy(vector1));
case VPLANEDIST:
return Py::asObject(new QuantityPy(new Quantity(vector1.DistanceToPlane(vector2, vector3), Unit::Length)));
case VPLANEPROJ:
vector1.ProjectToPlane(vector2, vector3);
return Py::asObject(new Base::VectorPy(vector1));
}
}
}
Py::Object e1 = args[0]->getPyValue();
@@ -2623,6 +2739,32 @@ void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const
ss << "tanh("; break;;
case TRUNC:
ss << "trunc("; break;;
case VANGLE:
ss << "vangle("; break;;
case VCROSS:
ss << "vcross("; break;;
case VDOT:
ss << "vdot("; break;;
case VLINEDIST:
ss << "vlinedist("; break;;
case VLINESEGDIST:
ss << "vlinesegdist("; break;;
case VLINEPROJ:
ss << "vlineproj("; break;;
case VNORMALIZE:
ss << "vnormalize("; break;;
case VPLANEDIST:
ss << "vplanedist("; break;;
case VPLANEPROJ:
ss << "vplaneproj("; break;;
case VSCALE:
ss << "vscale("; break;;
case VSCALEX:
ss << "vscalex("; break;;
case VSCALEY:
ss << "vscaley("; break;;
case VSCALEZ:
ss << "vscalez("; break;;
case MINVERT:
ss << "minvert("; break;;
case MROTATE:
@@ -3495,6 +3637,19 @@ static void initParser(const App::DocumentObject *owner)
registered_functions["tan"] = FunctionExpression::TAN;
registered_functions["tanh"] = FunctionExpression::TANH;
registered_functions["trunc"] = FunctionExpression::TRUNC;
registered_functions["vangle"] = FunctionExpression::VANGLE;
registered_functions["vcross"] = FunctionExpression::VCROSS;
registered_functions["vdot"] = FunctionExpression::VDOT;
registered_functions["vlinedist"] = FunctionExpression::VLINEDIST;
registered_functions["vlinesegdist"] = FunctionExpression::VLINESEGDIST;
registered_functions["vlineproj"] = FunctionExpression::VLINEPROJ;
registered_functions["vnormalize"] = FunctionExpression::VNORMALIZE;
registered_functions["vplanedist"] = FunctionExpression::VPLANEDIST;
registered_functions["vplaneproj"] = FunctionExpression::VPLANEPROJ;
registered_functions["vscale"] = FunctionExpression::VSCALE;
registered_functions["vscalex"] = FunctionExpression::VSCALEX;
registered_functions["vscaley"] = FunctionExpression::VSCALEY;
registered_functions["vscalez"] = FunctionExpression::VSCALEZ;
registered_functions["minvert"] = FunctionExpression::MINVERT;
registered_functions["mrotate"] = FunctionExpression::MROTATE;

View File

@@ -271,6 +271,21 @@ public:
TANH,
TRUNC,
// Vector
VANGLE,
VCROSS,
VDOT,
VLINEDIST,
VLINESEGDIST,
VLINEPROJ,
VNORMALIZE,
VPLANEDIST,
VPLANEPROJ,
VSCALE,
VSCALEX,
VSCALEY,
VSCALEZ,
// Matrix
MINVERT, // invert matrix/placement/rotation
MROTATE, // Rotate matrix/placement/rotation around axis, by rotation object, or by euler angles.
@@ -328,6 +343,8 @@ 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 double extractLengthValueArgument(const Expression *expression, const std::vector<Expression*> &arguments, int argumentIndex);
static Base::Vector3d extractVectorArgument(const Expression *expression, const std::vector<Expression*> &arguments, int argumentIndex);
static void initialiseObject(const Py::Object *object, const std::vector<Expression*> &arguments, const unsigned long offset = 0);
static Py::Object transformFirstArgument(
const Expression *expression,

View File

@@ -22,6 +22,7 @@
import os
import sys
import math
from math import sqrt
import unittest
import FreeCAD
import Part
@@ -1463,6 +1464,53 @@ class SpreadsheetCases(unittest.TestCase):
self.assertEqual(sheet.getContents('A1'), '\'36C')
self.assertEqual(sheet.get('A1'), '36C')
def testVectorFunctions(self):
sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet')
sheet.set('A1', '=vcross(vector(1; 2; 3); vector(1; 5; 7))')
sheet.set('B1', '=vdot(vector(1; 2; 3); vector(4; -5; 6))')
sheet.set('C1', '=vangle(vector(1; 0; 0); vector(0; 1; 0))')
sheet.set('D1', '=vnormalize(vector(1; 0; 0))')
sheet.set('D2', '=vnormalize(vector(1; 1; 1))')
sheet.set('E1', '=vscale(vector(1; 2; 3); 2; 3; 4)')
sheet.set('E2', '=vscalex(vector(1; 2; 3); -2)')
sheet.set('E3', '=vscaley(vector(1; 2; 3); -2)')
sheet.set('E4', '=vscalez(vector(1; 2; 3); -2)')
sheet.set('F1', '=vlinedist(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))')
sheet.set('F2', '=vlinesegdist(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))')
sheet.set('F3', '=vlineproj(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))')
sheet.set('F4', '=vplanedist(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))')
sheet.set('F5', '=vplaneproj(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))')
self.doc.recompute()
tolerance = 1e-10
self.assertEqual(sheet.A1, FreeCAD.Vector(-1, -4, 3))
self.assertEqual(sheet.B1, 12)
self.assertEqual(sheet.C1, 90)
self.assertEqual(sheet.D1, FreeCAD.Vector(1, 0, 0))
self.assertLess(sheet.D2.distanceToPoint(FreeCAD.Vector(1/sqrt(3), 1/sqrt(3), 1/sqrt(3))), tolerance)
self.assertEqual(sheet.E1, FreeCAD.Vector(2, 6, 12))
self.assertEqual(sheet.E2, FreeCAD.Vector(-2, 2, 3))
self.assertEqual(sheet.E3, FreeCAD.Vector(1, -4, 3))
self.assertEqual(sheet.E4, FreeCAD.Vector(1, 2, -6))
self.assertLess(abs(sheet.F1.Value - 0.3464), 0.0001)
self.assertEqual(sheet.F2, FreeCAD.Vector(1, 1, 1))
self.assertLess(sheet.F3.distanceToPoint(FreeCAD.Vector(0.28, 0.04, -0.2)), tolerance)
self.assertLess(abs(sheet.F4.Value - -1.6971), 0.0001)
self.assertEqual(sheet.F5, FreeCAD.Vector(1.72, 2.96, 4.2))
def tearDown(self):
#closing doc
FreeCAD.closeDocument(self.doc.Name)