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:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
//@}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user