Mesh: extend API to access edges of a facet via Python

This commit is contained in:
wmayer
2021-10-20 13:01:11 +02:00
parent ace0d32647
commit 124d06b7f7
14 changed files with 558 additions and 3 deletions

View File

@@ -33,6 +33,7 @@
#include "Mesh.h"
#include "MeshPy.h"
#include "MeshPointPy.h"
#include "EdgePy.h"
#include "FacetPy.h"
#include "MeshFeaturePy.h"
#include "FeatureMeshImport.h"
@@ -66,6 +67,7 @@ PyMOD_INIT_FUNC(Mesh)
// add mesh elements
Base::Interpreter().addType(&Mesh::MeshPointPy ::Type,meshModule,"MeshPoint");
Base::Interpreter().addType(&Mesh::EdgePy ::Type,meshModule,"Edge");
Base::Interpreter().addType(&Mesh::FacetPy ::Type,meshModule,"Facet");
Base::Interpreter().addType(&Mesh::MeshPy ::Type,meshModule,"Mesh");
Base::Interpreter().addType(&Mesh::MeshFeaturePy::Type,meshModule,"Feature");

View File

@@ -28,12 +28,14 @@ if (BUILD_QT5)
)
endif()
generate_from_xml(EdgePy)
generate_from_xml(FacetPy)
generate_from_xml(MeshFeaturePy)
generate_from_xml(MeshPointPy)
generate_from_xml(MeshPy)
SET(Mesh_XML_SRCS
EdgePy.xml
FacetPy.xml
MeshFeaturePy.xml
MeshPointPy.xml
@@ -331,6 +333,9 @@ SET(Mesh_SRCS
Exporter.h
Importer.cpp
Importer.h
Edge.cpp
Edge.h
EdgePyImp.cpp
Facet.cpp
Facet.h
FacetPyImp.cpp

View File

@@ -280,6 +280,25 @@ bool MeshGeomEdge::IntersectWithLine (const Base::Vector3f &rclPt,
return dist2 + dist3 <= dist1 + eps;
}
bool MeshGeomEdge::IsParallel(const MeshGeomEdge &edge) const
{
Base::Vector3f r(_aclPoints[1] - _aclPoints[0]);
Base::Vector3f s(edge._aclPoints[1] - edge._aclPoints[0]);
Base::Vector3f n = r.Cross(s);
return n.IsNull();
}
bool MeshGeomEdge::IsCollinear(const MeshGeomEdge &edge) const
{
if (IsParallel(edge)) {
Base::Vector3f r(_aclPoints[1] - _aclPoints[0]);
Base::Vector3f d = edge._aclPoints[0] - _aclPoints[0];
return d.Cross(r).IsNull();
}
return false;
}
bool MeshGeomEdge::IntersectWithEdge (const MeshGeomEdge &edge, Base::Vector3f &res) const
{
const float eps = 1e-06f;

View File

@@ -198,6 +198,16 @@ public:
* Checks if the projection point of \a point lies on the edge.
*/
bool IsProjectionPointOf(const Base::Vector3f& point) const;
/**
* Checks if the two edges are parallel.
* \note Parallel edges could be collinear.
*/
bool IsParallel(const MeshGeomEdge &edge) const;
/**
* Checks if the two edges are collinear.
* \note Collinear edges always are parallel.
*/
bool IsCollinear(const MeshGeomEdge &edge) const;
public:
Base::Vector3f _aclPoints[2]; /**< Corner points */

View File

@@ -41,7 +41,7 @@ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
*/
#include <math.h>
#include <cmath>
#define FABS(x) ((float)fabs(x)) /* implement as is fastest on your machine */

74
src/Mod/Mesh/App/Edge.cpp Normal file
View File

@@ -0,0 +1,74 @@
/***************************************************************************
* Copyright (c) 2021 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 51 Franklin Street, *
* Fifth Floor, Boston, MA 02110-1301, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <sstream>
#endif
#include "Edge.h"
#include "Mesh.h"
using namespace Mesh;
Edge::Edge()
: Index(-1)
, Mesh(nullptr)
{
for (int i=0; i<2; i++) {
PIndex[i] = MeshCore::POINT_INDEX_MAX;
NIndex[i] = MeshCore::FACET_INDEX_MAX;
}
}
Edge::Edge(const Edge& e)
: MeshCore::MeshGeomEdge(e)
, Index(e.Index)
, Mesh(e.Mesh)
{
for (int i=0; i<2; i++) {
PIndex[i] = e.PIndex[i];
NIndex[i] = e.NIndex[i];
}
}
Edge::~Edge()
{
}
void Edge::operator = (const Edge& e)
{
MeshCore::MeshGeomEdge::operator = (e);
Mesh = e.Mesh;
Index = e.Index;
for (int i=0; i<2; i++) {
PIndex[i] = e.PIndex[i];
NIndex[i] = e.NIndex[i];
}
}
void Edge::unbound()
{
Index = -1;
Mesh = nullptr;
}

63
src/Mod/Mesh/App/Edge.h Normal file
View File

@@ -0,0 +1,63 @@
/***************************************************************************
* Copyright (c) 2021 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 51 Franklin Street, *
* Fifth Floor, Boston, MA 02110-1301, USA *
* *
***************************************************************************/
#ifndef MESH_EDGE_H
#define MESH_EDGE_H
#include <Base/Matrix.h>
#include <Base/Vector3D.h>
#include <Base/Handle.h>
#include <Mod/Mesh/App/Core/Elements.h>
namespace Mesh
{
// forward declaration
class MeshObject;
/** The Edge helper class
* The Edge class provides an interface for the EdgePy class for
* convenient access to the Mesh data structure. This class should not be used
* for programming algorithms in C++. Use Mesh Core classes instead!
*/
class MeshExport Edge : public MeshCore::MeshGeomEdge
{
public:
Edge();
Edge(const Edge& f);
~Edge();
bool isBound() const {return Index != -1;}
void unbound();
void operator = (const Edge& f);
int Index;
MeshCore::PointIndex PIndex[2];
MeshCore::FacetIndex NIndex[2];
Base::Reference<MeshObject> Mesh;
};
} // namespace Mesh
#endif // MESH_EDGE_H

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
<PythonExport
Father="PyObjectBase"
Name="EdgePy"
Twin="Edge"
TwinPointer="Edge"
Include="Mod/Mesh/App/Edge.h"
FatherInclude="Base/PyObjectBase.h"
Namespace="Mesh"
Constructor="true"
Delete="true"
FatherNamespace="Base">
<Documentation>
<Author Licence="LGPL" Name="Werner Mayer" EMail="wmayer[at]users.sourceforge.net" />
<DeveloperDocu>Edge in a Mesh</DeveloperDocu>
<UserDocu>Edge in mesh
This is an edge of a facet in a MeshObject. You can get it by e.g. iterating over the facets of a
mesh and calling getEdge(index).
</UserDocu>
</Documentation>
<Methode Name="intersectWithEdge">
<Documentation>
<UserDocu>intersectWithEdge(Edge) -> list
Get a list of intersection points with another edge.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="isParallel">
<Documentation>
<UserDocu>isParallel(Edge) -> bool
Checks if the two edges are parallel.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="isCollinear">
<Documentation>
<UserDocu>isCollinear(Edge) -> bool
Checks if the two edges are collinear.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="unbound">
<Documentation>
<UserDocu>method unbound()
Cut the connection to a MeshObject. The edge becomes
free and is more or less a simple edge.
After calling unbound() no topological operation will
work!
</UserDocu>
</Documentation>
</Methode>
<Attribute Name="Index" ReadOnly="true">
<Documentation>
<UserDocu>The index of this edge of the facet</UserDocu>
</Documentation>
<Parameter Name="Index" Type="Long"/>
</Attribute>
<Attribute Name="Points" ReadOnly="true">
<Documentation>
<UserDocu>A list of points of the edge</UserDocu>
</Documentation>
<Parameter Name="Points" Type="List"/>
</Attribute>
<Attribute Name="PointIndices" ReadOnly="true">
<Documentation>
<UserDocu>The index tuple of point vertices of the mesh this edge is built of</UserDocu>
</Documentation>
<Parameter Name="PointIndices" Type="Tuple"/>
</Attribute>
<Attribute Name="NeighbourIndices" ReadOnly="true">
<Documentation>
<UserDocu>The index tuple of neighbour facets of the mesh this edge is adjacent with</UserDocu>
</Documentation>
<Parameter Name="NeighbourIndices" Type="Tuple"/>
</Attribute>
<Attribute Name="Length" ReadOnly="true">
<Documentation>
<UserDocu>The length of the edge</UserDocu>
</Documentation>
<Parameter Name="Length" Type="Float"/>
</Attribute>
<Attribute Name="Bound" ReadOnly="true">
<Documentation>
<UserDocu>Bound state of the edge</UserDocu>
</Documentation>
<Parameter Name="Bound" Type="Boolean"/>
</Attribute>
</PythonExport>
</GenerateModel>

View File

@@ -0,0 +1,194 @@
/***************************************************************************
* Copyright (c) 2021 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 51 Franklin Street, *
* Fifth Floor, Boston, MA 02110-1301, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#include "Mesh.h"
#include "Edge.h"
#include <Mod/Mesh/App/EdgePy.h>
#include <Mod/Mesh/App/EdgePy.cpp>
#include <Base/Converter.h>
#include <Base/VectorPy.h>
#include <Base/GeometryPyCXX.h>
using namespace Mesh;
// returns a string which represent the object e.g. when printed in python
std::string EdgePy::representation() const
{
EdgePy::PointerType ptr = getEdgePtr();
std::stringstream str;
str << "Edge (";
str << "(" << ptr->_aclPoints[0].x << ", " << ptr->_aclPoints[0].y << ", " << ptr->_aclPoints[0].z << ", Idx=" << ptr->PIndex[0] << "), ";
str << "(" << ptr->_aclPoints[1].x << ", " << ptr->_aclPoints[1].y << ", " << ptr->_aclPoints[1].z << ", Idx=" << ptr->PIndex[1] << "), ";
str << "Idx=" << ptr->Index << ", (" << ptr->NIndex[0] << ", " << ptr->NIndex[1] << ")";
str << ")";
return str.str();
}
PyObject *EdgePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper
{
// create a new instance of EdgePy and the Twin object
return new EdgePy(new Edge);
}
// constructor method
int EdgePy::PyInit(PyObject* args, PyObject* /*kwds*/)
{
PyObject* pt1 = nullptr;
PyObject* pt2 = nullptr;
if (!PyArg_ParseTuple(args, "|O!O!", &Base::VectorPy::Type, &pt1
, &Base::VectorPy::Type, &pt2))
return -1;
if (pt1)
getEdgePtr()->_aclPoints[0] = Base::convertTo<Base::Vector3f>(Py::Vector(pt1, false).toVector());
if (pt2)
getEdgePtr()->_aclPoints[1] = Base::convertTo<Base::Vector3f>(Py::Vector(pt2, false).toVector());
return 0;
}
Py::Long EdgePy::getIndex() const
{
return Py::Long((long) getEdgePtr()->Index);
}
PyObject* EdgePy::intersectWithEdge(PyObject *args)
{
PyObject* object;
if (!PyArg_ParseTuple(args, "O!", &EdgePy::Type, &object))
return nullptr;
EdgePy *edge = static_cast<EdgePy*>(object);
EdgePy::PointerType edge_ptr = edge->getEdgePtr();
EdgePy::PointerType this_ptr = this->getEdgePtr();
Base::Vector3f p;
bool ok = this_ptr->IntersectWithEdge(*edge_ptr, p);
try {
Py::List sct;
if (ok) {
Py::Tuple pt(3);
pt.setItem(0, Py::Float(p.x));
pt.setItem(1, Py::Float(p.y));
pt.setItem(2, Py::Float(p.z));
sct.append(pt);
}
return Py::new_reference_to(sct);
}
catch (const Py::Exception&) {
return nullptr;
}
}
PyObject* EdgePy::isParallel(PyObject *args)
{
PyObject* object;
if (!PyArg_ParseTuple(args, "O!", &EdgePy::Type, &object))
return nullptr;
EdgePy *edge = static_cast<EdgePy*>(object);
EdgePy::PointerType edge_ptr = edge->getEdgePtr();
EdgePy::PointerType this_ptr = this->getEdgePtr();
bool ok = this_ptr->IsParallel(*edge_ptr);
return Py::new_reference_to(Py::Boolean(ok));
}
PyObject* EdgePy::isCollinear(PyObject *args)
{
PyObject* object;
if (!PyArg_ParseTuple(args, "O!", &EdgePy::Type, &object))
return nullptr;
EdgePy *edge = static_cast<EdgePy*>(object);
EdgePy::PointerType edge_ptr = edge->getEdgePtr();
EdgePy::PointerType this_ptr = this->getEdgePtr();
bool ok = this_ptr->IsCollinear(*edge_ptr);
return Py::new_reference_to(Py::Boolean(ok));
}
PyObject* EdgePy::unbound(PyObject *args)
{
if (!PyArg_ParseTuple(args, ""))
return nullptr;
getEdgePtr()->unbound();
Py_Return;
}
Py::Boolean EdgePy::getBound() const
{
return Py::Boolean(getEdgePtr()->isBound());
}
Py::List EdgePy::getPoints() const
{
EdgePy::PointerType edge = this->getEdgePtr();
Py::List pts;
for (int i=0; i<2; i++) {
Py::Tuple pt(3);
pt.setItem(0, Py::Float(edge->_aclPoints[i].x));
pt.setItem(1, Py::Float(edge->_aclPoints[i].y));
pt.setItem(2, Py::Float(edge->_aclPoints[i].z));
pts.append(pt);
}
return pts;
}
Py::Tuple EdgePy::getPointIndices() const
{
EdgePy::PointerType edge = this->getEdgePtr();
Py::Tuple idxTuple(2);
for (int i=0; i<2; i++) {
idxTuple.setItem(i, Py::Long(edge->PIndex[i]));
}
return idxTuple;
}
Py::Tuple EdgePy::getNeighbourIndices() const
{
EdgePy::PointerType edge = this->getEdgePtr();
Py::Tuple idxTuple(2);
for (int i=0; i<2; i++) {
idxTuple.setItem(i, Py::Long(edge->NIndex[i]));
}
return idxTuple;
}
Py::Float EdgePy::getLength() const
{
EdgePy::PointerType edge = this->getEdgePtr();
return Py::Float(Base::Distance(edge->_aclPoints[0], edge->_aclPoints[1]));
}
PyObject *EdgePy::getCustomAttributes(const char* /*attr*/) const
{
return nullptr;
}
int EdgePy::setCustomAttributes(const char* /*attr*/, PyObject * /*obj*/)
{
return 0;
}

View File

@@ -69,3 +69,23 @@ void Facet::operator = (const Facet& f)
NIndex[i] = f.NIndex[i];
}
}
Edge Facet::getEdge(int index) const
{
index = index % 3;
Edge edge;
// geometric coordinates
edge._aclPoints[0] = this->_aclPoints[index];
edge._aclPoints[1] = this->_aclPoints[(index + 1) % 3];
// indices
edge.Index = index;
edge.PIndex[0] = this->PIndex[index];
edge.PIndex[1] = this->PIndex[(index + 1) % 3];
edge.NIndex[0] = this->Index;
edge.NIndex[1] = this->NIndex[index];
edge._bBorder = (this->NIndex[index] == MeshCore::FACET_INDEX_MAX);
edge.Mesh = this->Mesh;
return edge;
}

View File

@@ -28,7 +28,7 @@
#include <Base/Vector3D.h>
#include <Base/Handle.h>
#include "Core/Elements.h"
#include <Mod/Mesh/App/Edge.h>
namespace Mesh
{
@@ -49,6 +49,7 @@ public:
bool isBound() const {return Index != MeshCore::FACET_INDEX_MAX;}
void operator = (const Facet& f);
Edge getEdge(int) const;
MeshCore::FacetIndex Index;
MeshCore::PointIndex PIndex[3];

View File

@@ -56,6 +56,13 @@ The two angles are given in radian.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getEdge">
<Documentation>
<UserDocu>getEdge(int) -> Edge
Returns the edge of the facet.
</UserDocu>
</Documentation>
</Methode>
<Attribute Name="Index" ReadOnly="true">
<Documentation>
<UserDocu>The index of this facet in the MeshObject</UserDocu>

View File

@@ -27,6 +27,7 @@
#include "Facet.h"
#include <Mod/Mesh/App/FacetPy.h>
#include <Mod/Mesh/App/FacetPy.cpp>
#include <Mod/Mesh/App/EdgePy.h>
#include <Base/VectorPy.h>
#include <Base/GeometryPyCXX.h>
@@ -78,6 +79,16 @@ PyObject* FacetPy::unbound(PyObject *args)
Py_Return;
}
PyObject* FacetPy::getEdge(PyObject *args)
{
int index;
if (!PyArg_ParseTuple(args, "i", &index))
return nullptr;
Edge edge = getFacetPtr()->getEdge(index);
return new EdgePy(new Edge(edge));
}
Py::Long FacetPy::getIndex() const
{
return Py::Long((long) getFacetPtr()->Index);
@@ -85,7 +96,7 @@ Py::Long FacetPy::getIndex() const
Py::Boolean FacetPy::getBound() const
{
return Py::Boolean(getFacetPtr()->Index != UINT_MAX);
return Py::Boolean(getFacetPtr()->isBound());
}
Py::Object FacetPy::getNormal() const

View File

@@ -252,6 +252,65 @@ class MeshGeoTestCases(unittest.TestCase):
res = f1.intersect(f2)
self.assertTrue(len(res) == 2)
def testIntersectionOfIntersectingEdges(self):
self.planarMesh.append( [0.,10.,10.] )
self.planarMesh.append( [10.,0.,10.] )
self.planarMesh.append( [10.,10.,10.] )
self.planarMesh.append( [6.,8.,10.] )
self.planarMesh.append( [16.,8.,10.] )
self.planarMesh.append( [6.,18.,10.] )
planarMeshObject = Mesh.Mesh(self.planarMesh)
edge1 = planarMeshObject.Facets[0].getEdge(2)
edge2 = planarMeshObject.Facets[1].getEdge(2)
res = edge1.intersectWithEdge(edge2)
self.assertTrue(len(res) == 1)
self.assertEqual(res[0][0], 6.0)
self.assertEqual(res[0][1], 10.0)
self.assertEqual(res[0][2], 10.0)
def testIntersectionOfParallelEdges(self):
self.planarMesh.append( [0.,10.,10.] )
self.planarMesh.append( [10.,0.,10.] )
self.planarMesh.append( [10.,10.,10.] )
self.planarMesh.append( [6.,8.,10.] )
self.planarMesh.append( [16.,8.,10.] )
self.planarMesh.append( [6.,18.,10.] )
planarMeshObject = Mesh.Mesh(self.planarMesh)
edge1 = planarMeshObject.Facets[0].getEdge(2)
edge2 = planarMeshObject.Facets[1].getEdge(0)
res = edge1.intersectWithEdge(edge2)
self.assertTrue(len(res) == 0)
def testIntersectionOfCollinearEdges(self):
self.planarMesh.append( [0.,0.,0.] )
self.planarMesh.append( [6.,0.,0.] )
self.planarMesh.append( [3.,4.,0.] )
self.planarMesh.append( [7.,0.,0.] )
self.planarMesh.append( [13.,0.,0.] )
self.planarMesh.append( [10.,4.,0.] )
planarMeshObject = Mesh.Mesh(self.planarMesh)
edge1 = planarMeshObject.Facets[0].getEdge(0)
edge2 = planarMeshObject.Facets[1].getEdge(0)
res = edge1.intersectWithEdge(edge2)
self.assertTrue(len(res) == 0)
def testIntersectionOfWarpedEdges(self):
self.planarMesh.append( [0.,0.,0.] )
self.planarMesh.append( [6.,0.,0.] )
self.planarMesh.append( [3.,4.,0.] )
self.planarMesh.append( [2.,2.,1.] )
self.planarMesh.append( [8.,2.,1.] )
self.planarMesh.append( [5.,6.,1.] )
planarMeshObject = Mesh.Mesh(self.planarMesh)
edge1 = planarMeshObject.Facets[0].getEdge(1)
edge2 = planarMeshObject.Facets[1].getEdge(0)
res = edge1.intersectWithEdge(edge2)
self.assertTrue(len(res) == 0)
def testSelfIntersection(self):
s = b"""solid Simple
facet normal 0.0e0 0.0e0 1.0e1