diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index 4b6b258224..8ccc28934b 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -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 &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 &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(argument.ptr())->value(); +} + Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std::vector &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; diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index c985093435..90725a30c3 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -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 &args); static Base::Vector3d evaluateSecondVectorArgument(const Expression *expression, const std::vector &arguments); + static double extractLengthValueArgument(const Expression *expression, const std::vector &arguments, int argumentIndex); + static Base::Vector3d extractVectorArgument(const Expression *expression, const std::vector &arguments, int argumentIndex); static void initialiseObject(const Py::Object *object, const std::vector &arguments, const unsigned long offset = 0); static Py::Object transformFirstArgument( const Expression *expression, diff --git a/src/Mod/Spreadsheet/TestSpreadsheet.py b/src/Mod/Spreadsheet/TestSpreadsheet.py index 80b2ad3e04..9028b9cdd4 100644 --- a/src/Mod/Spreadsheet/TestSpreadsheet.py +++ b/src/Mod/Spreadsheet/TestSpreadsheet.py @@ -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)