diff --git a/src/App/Application.cpp b/src/App/Application.cpp index bf47e4d6e0..3af3f26fe8 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1990,9 +1990,14 @@ void Application::initTypes() App::TransactionalObject ::init(); App::DocumentObject ::init(); App::GeoFeature ::init(); + + // Test features App::FeatureTest ::init(); App::FeatureTestException ::init(); App::FeatureTestColumn ::init(); + App::FeatureTestPlacement ::init(); + + // Feature class App::FeaturePython ::init(); App::GeometryPython ::init(); App::Document ::init(); @@ -2004,7 +2009,7 @@ void Application::initTypes() App::Annotation ::init(); App::AnnotationLabel ::init(); App::MeasureDistance ::init(); - App ::MaterialObject ::init(); + App ::MaterialObject ::init(); App::MaterialObjectPython ::init(); App::TextDocument ::init(); App::Placement ::init(); diff --git a/src/App/FeatureTest.cpp b/src/App/FeatureTest.cpp index e40dfba616..2a142edee6 100644 --- a/src/App/FeatureTest.cpp +++ b/src/App/FeatureTest.cpp @@ -212,3 +212,26 @@ DocumentObjectExecReturn *FeatureTestColumn::execute() Value.setValue(decodeColumn(Column.getStrValue(), Silent.getValue())); return nullptr; } + +// ---------------------------------------------------------------------------- + +PROPERTY_SOURCE(App::FeatureTestPlacement, App::DocumentObject) + + +FeatureTestPlacement::FeatureTestPlacement() +{ + ADD_PROPERTY_TYPE(Input1, (Base::Placement()), "Test", Prop_None, ""); + ADD_PROPERTY_TYPE(Input2, (Base::Placement()), "Test", Prop_None, ""); + ADD_PROPERTY_TYPE(MultLeft, (Base::Placement()), "Test", Prop_Output, ""); + ADD_PROPERTY_TYPE(MultRight, (Base::Placement()), "Test", Prop_Output, ""); +} + +DocumentObjectExecReturn *FeatureTestPlacement::execute() +{ + Base::Placement p1 = Input1.getValue(); + Base::Placement q1 = Input1.getValue(); + Base::Placement p2 = Input2.getValue(); + MultLeft.setValue(p1.multLeft(p2)); + MultRight.setValue(q1.multRight(p2)); + return nullptr; +} diff --git a/src/App/FeatureTest.h b/src/App/FeatureTest.h index fc96f25667..ce54d6cb4c 100644 --- a/src/App/FeatureTest.h +++ b/src/App/FeatureTest.h @@ -152,6 +152,25 @@ public: //@} }; +class FeatureTestPlacement : public DocumentObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(App::FeatureTestPlacement); + +public: + FeatureTestPlacement(); + + // Standard Properties (PropertyStandard.h) + App::PropertyPlacement Input1; + App::PropertyPlacement Input2; + App::PropertyPlacement MultLeft; + App::PropertyPlacement MultRight; + + /** @name methods override Feature */ + //@{ + DocumentObjectExecReturn *execute() override; + //@} +}; + } //namespace App diff --git a/src/Base/Placement.cpp b/src/Base/Placement.cpp index 9402f5847c..50e261c982 100644 --- a/src/Base/Placement.cpp +++ b/src/Base/Placement.cpp @@ -97,6 +97,18 @@ bool Placement::isIdentity() const return none; } +bool Placement::isSame(const Placement& p) const +{ + return this->_rot.isSame(p._rot) && + this->_pos.IsEqual(p._pos, 0); +} + +bool Placement::isSame(const Placement& p, double tol) const +{ + return this->_rot.isSame(p._rot, tol) && + this->_pos.IsEqual(p._pos, tol); +} + void Placement::invert() { this->_rot = this->_rot.inverse(); @@ -126,13 +138,15 @@ bool Placement::operator != (const Placement& that) const return !(*this == that); } +/*! + Let this placement be right-multiplied by \a p. Returns reference to + self. + + \sa multRight() +*/ Placement & Placement::operator*=(const Placement & p) { - Base::Vector3d tmp(p._pos); - this->_rot.multVec(tmp, tmp); - this->_pos += tmp; - this->_rot *= p._rot; - return *this; + return multRight(p); } Placement Placement::operator*(const Placement & p) const @@ -154,6 +168,34 @@ Placement Placement::pow(double t, bool shorten) const return Placement::fromDualQuaternion(this->toDualQuaternion().pow(t, shorten)); } +/*! + Let this placement be right-multiplied by \a p. Returns reference to + self. + + \sa multLeft() +*/ +Placement& Placement::multRight(const Base::Placement& p) +{ + Base::Vector3d tmp(p._pos); + this->_rot.multVec(tmp, tmp); + this->_pos += tmp; + this->_rot.multRight(p._rot); + return *this; +} + +/*! + Let this placement be left-multiplied by \a p. Returns reference to + self. + + \sa multRight() +*/ +Placement& Placement::multLeft(const Base::Placement& p) +{ + p.multVec(this->_pos, this->_pos); + this->_rot.multLeft(p._rot); + return *this; +} + void Placement::multVec(const Vector3d & src, Vector3d & dst) const { this->_rot.multVec(src, dst); diff --git a/src/Base/Placement.h b/src/Base/Placement.h index f57d4b8d2b..7184046079 100644 --- a/src/Base/Placement.h +++ b/src/Base/Placement.h @@ -66,6 +66,9 @@ public: Placement inverse() const; void move(const Vector3d& MovVec); + bool isSame(const Placement&) const; + bool isSame(const Placement&, double tol) const; + /** Operators. */ //@{ Placement & operator*=(const Placement & p); @@ -75,6 +78,9 @@ public: Placement& operator = (const Placement&); Placement pow(double t, bool shorten = true) const; + Placement& multRight(const Base::Placement& p); + Placement& multLeft(const Base::Placement& p); + void multVec(const Vector3d & src, Vector3d & dst) const; void multVec(const Vector3f & src, Vector3f & dst) const; //@} diff --git a/src/Base/PlacementPy.xml b/src/Base/PlacementPy.xml index 5063315b41..58560ff021 100644 --- a/src/Base/PlacementPy.xml +++ b/src/Base/PlacementPy.xml @@ -149,6 +149,13 @@ Returns True if the placement has no displacement and no rotation. Matrix representation is the 4D identity matrix. + + + isSame(Base.Placement, [tol=0.0]) -> bool\n +Checks whether this and the given placement are the same. +The default tolerance is set to 0.0 + + Vector to the Base Position of the Placement. diff --git a/src/Base/PlacementPyImp.cpp b/src/Base/PlacementPyImp.cpp index fd80436251..9de81175b3 100644 --- a/src/Base/PlacementPyImp.cpp +++ b/src/Base/PlacementPyImp.cpp @@ -171,27 +171,23 @@ PyObject* PlacementPy::rotate(PyObject *args, PyObject *kwds) Vector3d axis; PyObject* pyComp = Py_False; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "(ddd)(ddd)d|$O!", keywords, ¢er.x, ¢er.y, ¢er.z, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "(ddd)(ddd)d|O!", keywords, ¢er.x, ¢er.y, ¢er.z, &axis.x, &axis.y, &axis.z, &angle, &PyBool_Type, &pyComp)) return nullptr; try { /* - * if comp is False, we retain the original behaviour that - contrary to the documentation - generates + * if comp is False, we retain the original behaviour that - contrary to the (old) documentation - generates * Placements different from TopoShape.rotate() to ensure compatibility for existing code */ bool comp = Base::asBoolean(pyComp); if (!comp) { - (*getPlacementPtr()) *= Placement( - Vector3d(),Rotation(axis,toRadians(angle)),center); - } else - { + getPlacementPtr()->multRight(Placement(Vector3d(), Rotation(axis, toRadians(angle)), center)); + } + else { // multiply new Placement the same way TopoShape.rotate() does - - Placement p = *getPlacementPtr(); - *getPlacementPtr() = Placement( - Vector3d(),Rotation(axis,toRadians(angle)),center) * p; + getPlacementPtr()->multLeft(Placement(Vector3d(), Rotation(axis, toRadians(angle)), center)); } Py_Return; @@ -285,6 +281,19 @@ PyObject* PlacementPy::isIdentity(PyObject *args) return Py_BuildValue("O", (none ? Py_True : Py_False)); } +PyObject* PlacementPy::isSame(PyObject *args) +{ + PyObject* plm; + double tol = 0.0; + if (!PyArg_ParseTuple(args, "O!|d", &PlacementPy::Type, &plm, &tol)) + return nullptr; + + Base::Placement plm1 = * getPlacementPtr(); + Base::Placement plm2 = * static_cast(plm)->getPlacementPtr(); + bool same = tol > 0.0 ? plm1.isSame(plm2, tol) : plm1.isSame(plm2); + return Py_BuildValue("O", (same ? Py_True : Py_False)); +} + Py::Object PlacementPy::getBase() const { return Py::Vector(getPlacementPtr()->getPosition()); diff --git a/src/Base/Rotation.cpp b/src/Base/Rotation.cpp index aab3af90c5..8212956a6c 100644 --- a/src/Base/Rotation.cpp +++ b/src/Base/Rotation.cpp @@ -351,7 +351,31 @@ Rotation Rotation::inverse() const return rot; } +/*! + Let this rotation be right-multiplied by \a q. Returns reference to + self. + + \sa multRight() +*/ Rotation & Rotation::operator*=(const Rotation & q) +{ + return multRight(q); +} + +Rotation Rotation::operator*(const Rotation & q) const +{ + Rotation quat(*this); + quat *= q; + return quat; +} + +/*! + Let this rotation be right-multiplied by \a q. Returns reference to + self. + + \sa multLeft() +*/ +Rotation& Rotation::multRight(const Base::Rotation& q) { // Taken from double x0, y0, z0, w0; @@ -366,11 +390,25 @@ Rotation & Rotation::operator*=(const Rotation & q) return *this; } -Rotation Rotation::operator*(const Rotation & q) const +/*! + Let this rotation be left-multiplied by \a q. Returns reference to + self. + + \sa multRight() +*/ +Rotation& Rotation::multLeft(const Base::Rotation& q) { - Rotation quat(*this); - quat *= q; - return quat; + // Taken from + double x0, y0, z0, w0; + q.getValue(x0, y0, z0, w0); + double x1, y1, z1, w1; + this->getValue(x1, y1, z1, w1); + + this->setValue(w0*x1 + x0*w1 + y0*z1 - z0*y1, + w0*y1 - x0*z1 + y0*w1 + z0*x1, + w0*z1 + x0*y1 - y0*x1 + z0*w1, + w0*w1 - x0*x1 - y0*y1 - z0*z1); + return *this; } bool Rotation::operator==(const Rotation & q) const diff --git a/src/Base/Rotation.h b/src/Base/Rotation.h index 64609439ee..990469e788 100644 --- a/src/Base/Rotation.h +++ b/src/Base/Rotation.h @@ -133,6 +133,9 @@ public: const double & operator [] (unsigned short usIndex) const{return quat[usIndex];} void operator = (const Rotation&); + Rotation& multRight(const Base::Rotation& q); + Rotation& multLeft(const Base::Rotation& q); + void multVec(const Vector3d & src, Vector3d & dst) const; Vector3d multVec(const Vector3d & src) const; void multVec(const Vector3f & src, Vector3f & dst) const; diff --git a/src/Mod/Part/TestPartApp.py b/src/Mod/Part/TestPartApp.py index 2e7496e296..140ef1a940 100644 --- a/src/Mod/Part/TestPartApp.py +++ b/src/Mod/Part/TestPartApp.py @@ -216,6 +216,29 @@ class PartTestNormals(unittest.TestCase): def tearDown(self): pass +class PartTestShapeRotate(unittest.TestCase): + def testPlacement(self): + box = Part.makeBox(1, 1, 1) + box.Placement.Base = Base.Vector(10, 10, 10) + box.rotate((0, 0, 0), (0, 0, 1), 90) + + p1 = Base.Placement() + p1.Base = Base.Vector(10, 10, 10) + + p2 = Base.Placement() + p2.Rotation.Angle = math.radians(90) + self.assertTrue(box.Placement.isSame(p2 * p1)) + + p5 = p1.copy() + p5.rotate((0, 0, 0), (0, 0, 1), 90) + self.assertTrue(p5.isSame(p1 * p2)) + self.assertFalse(box.Placement.isSame(p5)) + + p5 = p1.copy() + p5.rotate((0, 0, 0), (0, 0, 1), 90, True) + self.assertTrue(p5.isSame(p2 * p1)) + self.assertTrue(box.Placement.isSame(p5)) + class PartTestCircle2D(unittest.TestCase): def testValidCircle(self): p1 = App.Base.Vector2d(0.01, 0.01) diff --git a/src/Mod/Test/BaseTests.py b/src/Mod/Test/BaseTests.py index b07323552d..6cb265abc8 100644 --- a/src/Mod/Test/BaseTests.py +++ b/src/Mod/Test/BaseTests.py @@ -22,6 +22,7 @@ #***************************************************************************/ import FreeCAD, os, unittest, tempfile, math +from FreeCAD import Base class ConsoleTestCase(unittest.TestCase): def setUp(self): @@ -381,6 +382,27 @@ class AlgebraTestCase(unittest.TestCase): self.assertFalse(b.intersected(FreeCAD.BoundBox(4,4,4,6,6,6)).isValid(),"Bbox should not intersect with Bbox outside") self.assertEqual(b.intersected(FreeCAD.BoundBox(-2,-2,-2,2,2,2)).Center, b.Center,"Bbox is not a full subset") + def testMultLeftOrRight(self): + doc = FreeCAD.newDocument() + obj = doc.addObject("App::FeatureTestPlacement") + + p1 = Base.Placement() + p1.Base = Base.Vector(10, 10, 10) + + p2 = Base.Placement() + p2.Rotation.Angle = math.radians(90) + + obj.Input1 = p1 + obj.Input2 = p2 + doc.recompute() + + self.assertTrue(obj.MultRight.isSame(p1 * p2)) + self.assertFalse(obj.MultRight.isSame(p2 * p1)) + self.assertTrue(obj.MultLeft.isSame(p2 * p1)) + self.assertFalse(obj.MultLeft.isSame(p1 * p2)) + + FreeCAD.closeDocument(doc.Name) + class MatrixTestCase(unittest.TestCase): def setUp(self): self.mat = FreeCAD.Matrix()