Expressions: Add Vector API expression functions (#10237)
This commit is contained in:
committed by
GitHub
parent
a49e104993
commit
8003606222
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user