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()