ReverseEngineering: improve mesh segmentation

This commit is contained in:
wmayer
2020-03-04 23:04:06 +01:00
parent 2cda87f23a
commit cda6628f14
7 changed files with 252 additions and 72 deletions

View File

@@ -906,7 +906,7 @@ float CylinderFit::Fit()
return FLOAT_MAX;
_bIsFitted = true;
#if 0
#if 1
std::vector<Wm4::Vector3d> input;
std::transform(_vPoints.begin(), _vPoints.end(), std::back_inserter(input),
[](const Base::Vector3f& v) { return Wm4::Vector3d(v.x, v.y, v.z); });

View File

@@ -171,19 +171,23 @@ float PlaneSurfaceFit::GetDistanceToSurface(const Base::Vector3f& pnt) const
return fitter->GetDistanceToPlane(pnt);
}
Base::Vector3f PlaneSurfaceFit::Project(const Base::Vector3f& pt) const
std::vector<float> PlaneSurfaceFit::Parameters() const
{
Base::Vector3f prj(pt);
if (!fitter) {
prj.ProjectToPlane(basepoint, normal);
}
else {
Base::Vector3f base = fitter->GetBase();
Base::Vector3f norm = fitter->GetNormal();
prj.ProjectToPlane(base, norm);
Base::Vector3f base = basepoint;
Base::Vector3f norm = normal;
if (fitter) {
base = fitter->GetBase();
norm = fitter->GetNormal();
}
return prj;
std::vector<float> c;
c.push_back(base.x);
c.push_back(base.y);
c.push_back(base.z);
c.push_back(norm.x);
c.push_back(norm.y);
c.push_back(norm.z);
return c;
}
// --------------------------------------------------------
@@ -272,10 +276,26 @@ float CylinderSurfaceFit::GetDistanceToSurface(const Base::Vector3f& pnt) const
return (dist - radius);
}
Base::Vector3f CylinderSurfaceFit::Project(const Base::Vector3f& pt) const
std::vector<float> CylinderSurfaceFit::Parameters() const
{
//TODO
return pt;
Base::Vector3f base = basepoint;
Base::Vector3f norm = axis;
float radval = radius;
if (fitter) {
base = fitter->GetBase();
norm = fitter->GetAxis();
radval = fitter->GetRadius();
}
std::vector<float> c;
c.push_back(base.x);
c.push_back(base.y);
c.push_back(base.z);
c.push_back(norm.x);
c.push_back(norm.y);
c.push_back(norm.z);
c.push_back(radval);
return c;
}
// --------------------------------------------------------
@@ -353,10 +373,21 @@ float SphereSurfaceFit::GetDistanceToSurface(const Base::Vector3f& pnt) const
return (dist - radius);
}
Base::Vector3f SphereSurfaceFit::Project(const Base::Vector3f& pt) const
std::vector<float> SphereSurfaceFit::Parameters() const
{
//TODO
return pt;
Base::Vector3f base = center;
float radval = radius;
if (fitter) {
base = fitter->GetCenter();
radval = fitter->GetRadius();
}
std::vector<float> c;
c.push_back(base.x);
c.push_back(base.y);
c.push_back(base.z);
c.push_back(radval);
return c;
}
// --------------------------------------------------------
@@ -410,15 +441,9 @@ void MeshDistanceGenericSurfaceFitSegment::AddFacet(const MeshFacet& face)
fitter->AddTriangle(triangle);
}
std::vector<Base::Vector3f> MeshDistanceGenericSurfaceFitSegment::Project(const std::vector<Base::Vector3f>& pts) const
std::vector<float> MeshDistanceGenericSurfaceFitSegment::Parameters() const
{
std::vector<Base::Vector3f> prj;
prj.reserve(pts.size());
for (const auto it : pts) {
prj.push_back(fitter->Project(it));
}
return prj;
return fitter->Parameters();
}
// --------------------------------------------------------

View File

@@ -99,7 +99,7 @@ public:
virtual bool Done() const = 0;
virtual float Fit() = 0;
virtual float GetDistanceToSurface(const Base::Vector3f&) const = 0;
virtual Base::Vector3f Project(const Base::Vector3f&) const = 0;
virtual std::vector<float> Parameters() const = 0;
};
class MeshExport PlaneSurfaceFit : public AbstractSurfaceFit
@@ -115,7 +115,7 @@ public:
bool Done() const;
float Fit();
float GetDistanceToSurface(const Base::Vector3f&) const;
Base::Vector3f Project(const Base::Vector3f&) const;
std::vector<float> Parameters() const;
private:
Base::Vector3f basepoint;
@@ -136,7 +136,7 @@ public:
bool Done() const;
float Fit();
float GetDistanceToSurface(const Base::Vector3f&) const;
Base::Vector3f Project(const Base::Vector3f&) const;
std::vector<float> Parameters() const;
private:
Base::Vector3f basepoint;
@@ -158,7 +158,7 @@ public:
bool Done() const;
float Fit();
float GetDistanceToSurface(const Base::Vector3f&) const;
Base::Vector3f Project(const Base::Vector3f&) const;
std::vector<float> Parameters() const;
private:
Base::Vector3f center;
@@ -177,7 +177,7 @@ public:
void Initialize(unsigned long);
bool TestInitialFacet(unsigned long) const;
void AddFacet(const MeshFacet& rclFacet);
std::vector<Base::Vector3f> Project(const std::vector<Base::Vector3f>&) const;
std::vector<float> Parameters() const;
protected:
AbstractSurfaceFit* fitter;

View File

@@ -27,6 +27,7 @@
#endif
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <Gui/Application.h>
#include <Gui/Language/Translator.h>
#include "Workbench.h"
@@ -74,6 +75,15 @@ PyMOD_INIT_FUNC(ReverseEngineeringGui)
PyMOD_Return(0);
}
// load dependent module
try {
Base::Interpreter().loadModule("MeshGui");
}
catch(const Base::Exception& e) {
PyErr_SetString(PyExc_ImportError, e.what());
PyMOD_Return(0);
}
PyObject* mod = ReverseEngineeringGui::initModule();
Base::Console().Log("Loading GUI of ReverseEngineering module... done\n");

View File

@@ -34,11 +34,13 @@
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Part/App/PartFeature.h>
#include <Mod/Part/App/FaceMakerCheese.h>
#include <Mod/Points/App/Structured.h>
#include <Mod/Mesh/App/MeshFeature.h>
#include <Mod/Mesh/App/Core/Approximation.h>
#include <Mod/Mesh/App/Core/Algorithm.h>
#include <App/Application.h>
#include <App/Document.h>
#include <Gui/Application.h>
#include <Gui/Command.h>
@@ -98,7 +100,7 @@ CmdApproxPlane::CmdApproxPlane()
{
sAppModule = "Reen";
sGroup = QT_TR_NOOP("Reverse Engineering");
sMenuText = QT_TR_NOOP("Approximate plane...");
sMenuText = QT_TR_NOOP("Plane...");
sToolTipText = QT_TR_NOOP("Approximate a plane");
sWhatsThis = "Reen_ApproxPlane";
sStatusTip = sToolTipText;
@@ -192,6 +194,104 @@ bool CmdApproxPlane::isActive(void)
return false;
}
DEF_STD_CMD_A(CmdApproxCylinder)
CmdApproxCylinder::CmdApproxCylinder()
: Command("Reen_ApproxCylinder")
{
sAppModule = "Reen";
sGroup = QT_TR_NOOP("Reverse Engineering");
sMenuText = QT_TR_NOOP("Cylinder");
sToolTipText = QT_TR_NOOP("Approximate a cylinder");
sWhatsThis = "Reen_ApproxCylinder";
sStatusTip = sToolTipText;
}
void CmdApproxCylinder::activated(int)
{
std::vector<Mesh::Feature*> sel = getSelection().getObjectsOfType<Mesh::Feature>();
openCommand("Fit cylinder");
for (auto it : sel) {
const Mesh::MeshObject& mesh = it->Mesh.getValue();
const MeshCore::MeshKernel& kernel = mesh.getKernel();
MeshCore::CylinderFit fit;
fit.AddPoints(kernel.GetPoints());
if (fit.Fit() < FLOAT_MAX) {
Base::Vector3f base = fit.GetBase();
Base::Rotation rot;
rot.setValue(Base::Vector3d(0,0,1), Base::convertTo<Base::Vector3d>(fit.GetAxis()));
double q0, q1, q2, q3;
rot.getValue(q0, q1, q2, q3);
std::stringstream str;
str << "from FreeCAD import Base" << std::endl;
str << "App.ActiveDocument.addObject('Part::Cylinder','Cylinder_fit')" << std::endl;
str << "App.ActiveDocument.ActiveObject.Radius = " << fit.GetRadius() << std::endl;
str << "App.ActiveDocument.ActiveObject.Placement = Base.Placement("
<< "Base.Vector(" << base.x << "," << base.y << "," << base.z << "),"
<< "Base.Rotation(" << q0 << "," << q1 << "," << q2 << "," << q3 << "))" << std::endl;
runCommand(Gui::Command::Doc, str.str().c_str());
}
}
commitCommand();
updateActive();
}
bool CmdApproxCylinder::isActive(void)
{
if (getSelection().countObjectsOfType(Mesh::Feature::getClassTypeId()) > 0)
return true;
return false;
}
DEF_STD_CMD_A(CmdApproxSphere)
CmdApproxSphere::CmdApproxSphere()
: Command("Reen_ApproxSphere")
{
sAppModule = "Reen";
sGroup = QT_TR_NOOP("Reverse Engineering");
sMenuText = QT_TR_NOOP("Sphere");
sToolTipText = QT_TR_NOOP("Approximate a sphere");
sWhatsThis = "Reen_ApproxSphere";
sStatusTip = sToolTipText;
}
void CmdApproxSphere::activated(int)
{
std::vector<Mesh::Feature*> sel = getSelection().getObjectsOfType<Mesh::Feature>();
openCommand("Fit sphere");
for (auto it : sel) {
const Mesh::MeshObject& mesh = it->Mesh.getValue();
const MeshCore::MeshKernel& kernel = mesh.getKernel();
MeshCore::SphereFit fit;
fit.AddPoints(kernel.GetPoints());
if (fit.Fit() < FLOAT_MAX) {
Base::Vector3f base = fit.GetCenter();
std::stringstream str;
str << "from FreeCAD import Base" << std::endl;
str << "App.ActiveDocument.addObject('Part::Sphere','Sphere_fit')" << std::endl;
str << "App.ActiveDocument.ActiveObject.Radius = " << fit.GetRadius() << std::endl;
str << "App.ActiveDocument.ActiveObject.Placement = Base.Placement("
<< "Base.Vector(" << base.x << "," << base.y << "," << base.z << "),"
<< "Base.Rotation(" << 1 << "," << 0 << "," << 0 << "," << 0 << "))" << std::endl;
runCommand(Gui::Command::Doc, str.str().c_str());
}
}
commitCommand();
updateActive();
}
bool CmdApproxSphere::isActive(void)
{
if (getSelection().countObjectsOfType(Mesh::Feature::getClassTypeId()) > 0)
return true;
return false;
}
DEF_STD_CMD_A(CmdSegmentation)
CmdSegmentation::CmdSegmentation()
@@ -199,7 +299,7 @@ CmdSegmentation::CmdSegmentation()
{
sAppModule = "Reen";
sGroup = QT_TR_NOOP("Reverse Engineering");
sMenuText = QT_TR_NOOP("Create mesh segments...");
sMenuText = QT_TR_NOOP("Mesh segmentation...");
sToolTipText = QT_TR_NOOP("Create mesh segments");
sWhatsThis = "Reen_Segmentation";
sStatusTip = sToolTipText;
@@ -231,8 +331,8 @@ CmdMeshBoundary::CmdMeshBoundary()
{
sAppModule = "Reen";
sGroup = QT_TR_NOOP("Reverse Engineering");
sMenuText = QT_TR_NOOP("Wire from mesh...");
sToolTipText = QT_TR_NOOP("Create wire from mesh");
sMenuText = QT_TR_NOOP("Wire from mesh boundary...");
sToolTipText = QT_TR_NOOP("Create wire from mesh boundaries");
sWhatsThis = "Reen_Segmentation";
sStatusTip = sToolTipText;
}
@@ -252,6 +352,9 @@ void CmdMeshBoundary::activated(int)
TopoDS_Compound compound;
builder.MakeCompound(compound);
TopoDS_Shape shape;
std::vector<TopoDS_Wire> wires;
for (auto bt = bounds.begin(); bt != bounds.end(); ++bt) {
BRepBuilderAPI_MakePolygon mkPoly;
for (std::vector<Base::Vector3f>::reverse_iterator it = bt->rbegin(); it != bt->rend(); ++it) {
@@ -259,12 +362,24 @@ void CmdMeshBoundary::activated(int)
}
if (mkPoly.IsDone()) {
builder.Add(compound, mkPoly.Wire());
wires.push_back(mkPoly.Wire());
}
}
Part::Feature* shapeFea = static_cast<Part::Feature*>(document->addObject("Part::Feature", "Wires from mesh"));
shapeFea->Shape.setValue(compound);
try {
shape = Part::FaceMakerCheese::makeFace(wires);
}
catch (...) {
}
if (!shape.IsNull()) {
Part::Feature* shapeFea = static_cast<Part::Feature*>(document->addObject("Part::Feature", "Face from mesh"));
shapeFea->Shape.setValue(shape);
}
else {
Part::Feature* shapeFea = static_cast<Part::Feature*>(document->addObject("Part::Feature", "Wire from mesh"));
shapeFea->Shape.setValue(compound);
}
}
document->commitTransaction();
}
@@ -366,6 +481,8 @@ void CreateReverseEngineeringCommands(void)
Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
rcCmdMgr.addCommand(new CmdApproxSurface());
rcCmdMgr.addCommand(new CmdApproxPlane());
rcCmdMgr.addCommand(new CmdApproxCylinder());
rcCmdMgr.addCommand(new CmdApproxSphere());
rcCmdMgr.addCommand(new CmdSegmentation());
rcCmdMgr.addCommand(new CmdMeshBoundary());
rcCmdMgr.addCommand(new CmdPoissonReconstruction());

View File

@@ -27,6 +27,8 @@
# include <sstream>
# include <BRep_Builder.hxx>
# include <BRepBuilderAPI_MakePolygon.hxx>
# include <GeomAPI_ProjectPointOnSurf.hxx>
# include <Geom_Plane.hxx>
# include <Standard_Failure.hxx>
# include <TopoDS_Compound.hxx>
# include <TopoDS_Wire.hxx>
@@ -94,6 +96,7 @@ void Segmentation::accept()
MeshCore::MeshCurvature meshCurv(kernel);
meshCurv.ComputePerVertex();
// First create segments by curavture to get the surface type
std::vector<MeshCore::MeshSurfaceSegmentPtr> segm;
if (ui->groupBoxPln->isChecked()) {
segm.emplace_back(new MeshCore::MeshCurvaturePlanarSegment
@@ -101,20 +104,23 @@ void Segmentation::accept()
}
finder.FindSegments(segm);
// For each planar segment compute a plane and use this then for a more accurate 2nd segmentation
std::vector<MeshCore::MeshSurfaceSegmentPtr> segmSurf;
for (std::vector<MeshCore::MeshSurfaceSegmentPtr>::iterator it = segm.begin(); it != segm.end(); ++it) {
const std::vector<MeshCore::MeshSegment>& data = (*it)->GetSegments();
for (std::vector<MeshCore::MeshSegment>::const_iterator jt = data.begin(); jt != data.end(); ++jt) {
std::vector<unsigned long> indexes = kernel.GetFacetPoints(*jt);
MeshCore::PlaneFit fit;
fit.AddPoints(kernel.GetPoints(indexes));
if (fit.Fit() < FLOAT_MAX) {
Base::Vector3f base = fit.GetBase();
Base::Vector3f axis = fit.GetNormal();
MeshCore::AbstractSurfaceFit* fitter = new MeshCore::PlaneSurfaceFit(base, axis);
segmSurf.emplace_back(new MeshCore::MeshDistanceGenericSurfaceFitSegment
(fitter, kernel, ui->numPln->value(), ui->distToPln->value()));
// For each planar segment compute a plane and use this then for a more accurate 2nd segmentation
if (strcmp((*it)->GetType(), "Plane") == 0) {
for (std::vector<MeshCore::MeshSegment>::const_iterator jt = data.begin(); jt != data.end(); ++jt) {
std::vector<unsigned long> indexes = kernel.GetFacetPoints(*jt);
MeshCore::PlaneFit fit;
fit.AddPoints(kernel.GetPoints(indexes));
if (fit.Fit() < FLOAT_MAX) {
Base::Vector3f base = fit.GetBase();
Base::Vector3f axis = fit.GetNormal();
MeshCore::AbstractSurfaceFit* fitter = new MeshCore::PlaneSurfaceFit(base, axis);
segmSurf.emplace_back(new MeshCore::MeshDistanceGenericSurfaceFitSegment
(fitter, kernel, ui->numPln->value(), ui->distToPln->value()));
}
}
}
}
@@ -140,6 +146,7 @@ void Segmentation::accept()
std::shared_ptr<MeshCore::MeshDistanceGenericSurfaceFitSegment> genSegm = std::dynamic_pointer_cast
<MeshCore::MeshDistanceGenericSurfaceFitSegment>(*it);
bool isPlanar = (strcmp(genSegm->GetType(), "Plane") == 0);
for (std::vector<MeshCore::MeshSegment>::const_iterator jt = data.begin(); jt != data.end(); ++jt) {
// reset flag for facets of segment
algo.ResetFacetsFlag(*jt, MeshCore::MeshFacet::TMP0);
@@ -159,33 +166,47 @@ void Segmentation::accept()
std::list<std::vector<Base::Vector3f> > bounds;
algo.GetFacetBorders(*jt, bounds);
std::vector<TopoDS_Wire> wires;
for (auto bt = bounds.begin(); bt != bounds.end(); ++bt) {
// project the points onto the surface
auto prj = genSegm->Project(*bt);
BRepBuilderAPI_MakePolygon mkPoly;
for (std::vector<Base::Vector3f>::reverse_iterator it = prj.rbegin(); it != prj.rend(); ++it) {
mkPoly.Add(gp_Pnt(it->x,it->y,it->z));
}
if (mkPoly.IsDone()) {
wires.push_back(mkPoly.Wire());
}
}
// Handle planar segments
if (isPlanar) {
std::vector<float> par = genSegm->Parameters();
gp_Pnt loc(par.at(0), par.at(1), par.at(2));
gp_Dir dir(par.at(3), par.at(4), par.at(5));
try {
TopoDS_Shape shape = Part::FaceMakerCheese::makeFace(wires);
if (!shape.IsNull()) {
builder.Add(compound, shape);
Handle(Geom_Plane) hPlane(new Geom_Plane(loc, dir));
std::vector<TopoDS_Wire> wires;
for (auto bt = bounds.begin(); bt != bounds.end(); ++bt) {
// project the points onto the surface
std::vector<gp_Pnt> polygon;
std::transform(bt->begin(), bt->end(), std::back_inserter(polygon), [&hPlane](const Base::Vector3f v) {
gp_Pnt p(v.x, v.y, v.z);
return GeomAPI_ProjectPointOnSurf(p, hPlane).NearestPoint();
});
BRepBuilderAPI_MakePolygon mkPoly;
for (std::vector<gp_Pnt>::reverse_iterator it = polygon.rbegin(); it != polygon.rend(); ++it) {
mkPoly.Add(*it);
}
if (mkPoly.IsDone()) {
wires.push_back(mkPoly.Wire());
}
}
else {
try {
TopoDS_Shape shape = Part::FaceMakerCheese::makeFace(wires);
if (!shape.IsNull()) {
builder.Add(compound, shape);
}
else {
failures.push_back(feaSegm);
Base::Console().Warning("Failed to create face from %s\n", feaSegm->Label.getValue());
}
}
catch (Standard_Failure&) {
failures.push_back(feaSegm);
Base::Console().Warning("Failed to create face from %s\n", feaSegm->Label.getValue());
Base::Console().Error("Fatal failure to create face from %s\n", feaSegm->Label.getValue());
}
}
catch (Standard_Failure&) {
failures.push_back(feaSegm);
Base::Console().Error("Fatal failure to create face from %s\n", feaSegm->Label.getValue());
}
}
}
}

View File

@@ -54,9 +54,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
Gui::MenuItem* item = root->findItem("&Windows");
Gui::MenuItem* reen = new Gui::MenuItem;
root->insertItem(item, reen);
reen->setCommand("&REEN");
*reen << "Reen_ApproxPlane"
<< "Reen_ApproxSurface";
reen->setCommand("&Reverse Engineering");
Gui::MenuItem *reconstruct = new Gui::MenuItem();
reconstruct->setCommand("Surface reconstruction");
@@ -74,6 +72,15 @@ Gui::MenuItem* Workbench::setupMenuBar() const
<< "Reen_MeshBoundary";
*reen << segm;
Gui::MenuItem *approx = new Gui::MenuItem();
approx->setCommand("Approximation");
*approx << "Reen_ApproxPlane"
<< "Reen_ApproxCylinder"
<< "Reen_ApproxSphere"
<< "Separator"
<< "Reen_ApproxSurface";
*reen << approx;
return root;
}