Base: extend Placement/Rotation API

* Add Placement::isSame() and expose to Python
* Add Placement::multRight/Placement::multLeft
* Fix PlacementPy::rotate
* Add Rotation::multRight/Rotation::multLeft
* Add a test feature FeatureTestPlacement for uni tests
* Add unit tests
This commit is contained in:
wmayer
2022-08-09 11:54:05 +02:00
parent b35f66e7c6
commit 00bdd16dff
11 changed files with 217 additions and 20 deletions

View File

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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
//@}

View File

@@ -149,6 +149,13 @@ Returns True if the placement has no displacement and no rotation.
Matrix representation is the 4D identity matrix.</UserDocu>
</Documentation>
</Methode>
<Methode Name="isSame" Const="true">
<Documentation>
<UserDocu>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</UserDocu>
</Documentation>
</Methode>
<Attribute Name="Base" ReadOnly="false">
<Documentation>
<UserDocu>Vector to the Base Position of the Placement.</UserDocu>

View File

@@ -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, &center.x, &center.y, &center.z,
if (!PyArg_ParseTupleAndKeywords(args, kwds, "(ddd)(ddd)d|O!", keywords, &center.x, &center.y, &center.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<double>(angle)),center);
} else
{
getPlacementPtr()->multRight(Placement(Vector3d(), Rotation(axis, toRadians<double>(angle)), center));
}
else {
// multiply new Placement the same way TopoShape.rotate() does
Placement p = *getPlacementPtr();
*getPlacementPtr() = Placement(
Vector3d(),Rotation(axis,toRadians<double>(angle)),center) * p;
getPlacementPtr()->multLeft(Placement(Vector3d(), Rotation(axis, toRadians<double>(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<PlacementPy*>(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());

View File

@@ -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 <http://de.wikipedia.org/wiki/Quaternionen>
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 <http://de.wikipedia.org/wiki/Quaternionen>
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

View File

@@ -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;

View File

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

View File

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