From 7eb7be3936ebd902f59fffce2d4262568edbb221 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 2 Dec 2015 23:36:26 +0100 Subject: [PATCH] + add Poisson surface reconstruction --- .../App/AppReverseEngineering.cpp | 30 ++++ .../App/SurfaceTriangulation.cpp | 108 ++++++++++--- .../App/SurfaceTriangulation.h | 48 ++++++ src/Mod/ReverseEngineering/Gui/CMakeLists.txt | 4 + src/Mod/ReverseEngineering/Gui/Command.cpp | 36 +++++ src/Mod/ReverseEngineering/Gui/Poisson.cpp | 149 ++++++++++++++++++ src/Mod/ReverseEngineering/Gui/Poisson.h | 73 +++++++++ src/Mod/ReverseEngineering/Gui/Poisson.ui | 90 +++++++++++ src/Mod/ReverseEngineering/Gui/Workbench.cpp | 9 +- 9 files changed, 528 insertions(+), 19 deletions(-) create mode 100644 src/Mod/ReverseEngineering/Gui/Poisson.cpp create mode 100644 src/Mod/ReverseEngineering/Gui/Poisson.h create mode 100644 src/Mod/ReverseEngineering/Gui/Poisson.ui diff --git a/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp b/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp index bd20cbd7fe..ff2f32ce35 100644 --- a/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp +++ b/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp @@ -62,6 +62,9 @@ public: add_varargs_method("triangulate",&Module::triangulate, "triangulate(PointKernel,searchRadius[,mu=2.5])." ); + add_keyword_method("poissonReconstruction",&Module::poissonReconstruction, + "poissonReconstruction(PointKernel)." + ); #endif #if defined(HAVE_PCL_OPENNURBS) add_keyword_method("fitBSpline",&Module::fitBSpline, @@ -211,6 +214,33 @@ private: SurfaceTriangulation tria(*points, *mesh); tria.perform(searchRadius, mu); + return Py::asObject(new Mesh::MeshPy(mesh)); + } + Py::Object poissonReconstruction(const Py::Tuple& args, const Py::Dict& kwds) + { + PyObject *pcObj; + int ksearch=5; + int octreeDepth=-1; + int solverDivide=-1; + double samplesPerNode=-1.0; + + static char* kwds_poisson[] = {"Points", "KSearch", "OctreeDepth", "SolverDivide", + "SamplesPerNode", NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iiid", kwds_poisson, + &(Points::PointsPy::Type), &pcObj, + &ksearch, &octreeDepth, &solverDivide, &samplesPerNode)) + throw Py::Exception(); + + Points::PointsPy* pPoints = static_cast(pcObj); + Points::PointKernel* points = pPoints->getPointKernelPtr(); + + Mesh::MeshObject* mesh = new Mesh::MeshObject(); + Reen::PoissonReconstruction poisson(*points, *mesh); + poisson.setDepth(octreeDepth); + poisson.setSolverDivide(solverDivide); + poisson.setSamplesPerNode(samplesPerNode); + poisson.perform(ksearch); + return Py::asObject(new Mesh::MeshPy(mesh)); } #endif diff --git a/src/Mod/ReverseEngineering/App/SurfaceTriangulation.cpp b/src/Mod/ReverseEngineering/App/SurfaceTriangulation.cpp index b58a7a1512..96af584374 100644 --- a/src/Mod/ReverseEngineering/App/SurfaceTriangulation.cpp +++ b/src/Mod/ReverseEngineering/App/SurfaceTriangulation.cpp @@ -38,6 +38,7 @@ #include #include #include +#include //#include //#include #include @@ -108,11 +109,86 @@ void SurfaceTriangulation::perform(double searchRadius, double mu) PolygonMesh mesh; gp3.reconstruct (mesh); + MeshConversion::convert(mesh, myMesh); + + // Additional vertex information + //std::vector parts = gp3.getPartIDs(); + //std::vector states = gp3.getPointStates(); +} + +// ---------------------------------------------------------------------------- + +// See +// http://www.cs.jhu.edu/~misha/Code/PoissonRecon/Version8.0/ +PoissonReconstruction::PoissonReconstruction(const Points::PointKernel& pts, Mesh::MeshObject& mesh) + : myPoints(pts) + , myMesh(mesh) + , depth(-1) + , solverDivide(-1) + , samplesPerNode(-1.0f) +{ +} + +void PoissonReconstruction::perform(int ksearch) +{ + PointCloud::Ptr cloud (new PointCloud); + PointCloud::Ptr cloud_with_normals (new PointCloud); + search::KdTree::Ptr tree; + search::KdTree::Ptr tree2; + + for (Points::PointKernel::const_iterator it = myPoints.begin(); it != myPoints.end(); ++it) { + cloud->push_back(PointXYZ(it->x, it->y, it->z)); + } + + // Create search tree + tree.reset (new search::KdTree (false)); + tree->setInputCloud (cloud); + + // Normal estimation + NormalEstimation n; + PointCloud::Ptr normals (new PointCloud ()); + n.setInputCloud (cloud); + //n.setIndices (indices[B); + n.setSearchMethod (tree); + n.setKSearch (ksearch); + n.compute (*normals); + + // Concatenate XYZ and normal information + pcl::concatenateFields (*cloud, *normals, *cloud_with_normals); + + // Create search tree + tree2.reset (new search::KdTree); + tree2->setInputCloud (cloud_with_normals); + + // Init objects + Poisson poisson; + + // Set parameters + poisson.setInputCloud (cloud_with_normals); + poisson.setSearchMethod (tree2); + if (depth >= 1) + poisson.setDepth(depth); + if (solverDivide >= 1) + poisson.setSolverDivide(solverDivide); + if (samplesPerNode >= 1.0f) + poisson.setSamplesPerNode(samplesPerNode); + + // Reconstruct + PolygonMesh mesh; + poisson.reconstruct (mesh); + + MeshConversion::convert(mesh, myMesh); +} + +// ---------------------------------------------------------------------------- + +void MeshConversion::convert(const pcl::PolygonMesh& pclMesh, Mesh::MeshObject& meshObject) +{ // number of points - size_t nr_points = mesh.cloud.width * mesh.cloud.height; - size_t point_size = mesh.cloud.data.size () / nr_points; + size_t nr_points = pclMesh.cloud.width * pclMesh.cloud.height; + size_t point_size = pclMesh.cloud.data.size () / nr_points; // number of faces for header - size_t nr_faces = mesh.polygons.size (); + size_t nr_faces = pclMesh.polygons.size (); MeshCore::MeshPointArray points; points.reserve(nr_points); @@ -123,21 +199,21 @@ void SurfaceTriangulation::perform(double searchRadius, double mu) MeshCore::MeshPoint vertex; for (size_t i = 0; i < nr_points; ++i) { int xyz = 0; - for (size_t d = 0; d < mesh.cloud.fields.size(); ++d) { + for (size_t d = 0; d < pclMesh.cloud.fields.size(); ++d) { int c = 0; // adding vertex - if ((mesh.cloud.fields[d].datatype == + if ((pclMesh.cloud.fields[d].datatype == #if PCL_VERSION_COMPARE(>,1,6,0) pcl::PCLPointField::FLOAT32) && #else sensor_msgs::PointField::FLOAT32) && #endif - (mesh.cloud.fields[d].name == "x" || - mesh.cloud.fields[d].name == "y" || - mesh.cloud.fields[d].name == "z")) + (pclMesh.cloud.fields[d].name == "x" || + pclMesh.cloud.fields[d].name == "y" || + pclMesh.cloud.fields[d].name == "z")) { float value; - memcpy (&value, &mesh.cloud.data[i * point_size + mesh.cloud.fields[d].offset + c * sizeof (float)], sizeof (float)); + memcpy (&value, &pclMesh.cloud.data[i * point_size + pclMesh.cloud.fields[d].offset + c * sizeof (float)], sizeof (float)); vertex[xyz] = value; if (++xyz == 3) { points.push_back(vertex); @@ -149,20 +225,16 @@ void SurfaceTriangulation::perform(double searchRadius, double mu) // get faces MeshCore::MeshFacet face; for (size_t i = 0; i < nr_faces; i++) { - face._aulPoints[0] = mesh.polygons[i].vertices[0]; - face._aulPoints[1] = mesh.polygons[i].vertices[1]; - face._aulPoints[2] = mesh.polygons[i].vertices[2]; + face._aulPoints[0] = pclMesh.polygons[i].vertices[0]; + face._aulPoints[1] = pclMesh.polygons[i].vertices[1]; + face._aulPoints[2] = pclMesh.polygons[i].vertices[2]; facets.push_back(face); } MeshCore::MeshKernel kernel; kernel.Adopt(points, facets, true); - myMesh.swap(kernel); - myMesh.harmonizeNormals(); - - // Additional vertex information - //std::vector parts = gp3.getPartIDs(); - //std::vector states = gp3.getPointStates(); + meshObject.swap(kernel); + meshObject.harmonizeNormals(); } #endif // HAVE_PCL_SURFACE diff --git a/src/Mod/ReverseEngineering/App/SurfaceTriangulation.h b/src/Mod/ReverseEngineering/App/SurfaceTriangulation.h index 33d3545383..0e0fdf2003 100644 --- a/src/Mod/ReverseEngineering/App/SurfaceTriangulation.h +++ b/src/Mod/ReverseEngineering/App/SurfaceTriangulation.h @@ -26,9 +26,16 @@ namespace Points {class PointKernel;} namespace Mesh {class MeshObject;} +namespace pcl {struct PolygonMesh;} namespace Reen { +class MeshConversion +{ +public: + static void convert(const pcl::PolygonMesh&, Mesh::MeshObject&); +}; + class SurfaceTriangulation { public: @@ -40,6 +47,47 @@ private: Mesh::MeshObject& myMesh; }; +class PoissonReconstruction +{ +public: + PoissonReconstruction(const Points::PointKernel&, Mesh::MeshObject&); + void perform(int ksearch=5); + + /** \brief Set the maximum depth of the tree that will be used for surface reconstruction. + * \note Running at depth d corresponds to solving on a voxel grid whose resolution is no larger than + * 2^d x 2^d x 2^d. Note that since the reconstructor adapts the octree to the sampling density, the specified + * reconstruction depth is only an upper bound. + * \param[in] depth the depth parameter + */ + inline void + setDepth (int depth) { this->depth = depth; } + + /** \brief Set the the depth at which a block Gauss-Seidel solver is used to solve the Laplacian equation + * \note Using this parameter helps reduce the memory overhead at the cost of a small increase in + * reconstruction time. (In practice, we have found that for reconstructions of depth 9 or higher a subdivide + * depth of 7 or 8 can greatly reduce the memory usage.) + * \param[in] solver_divide the given parameter value + */ + inline void + setSolverDivide (int solverDivide) { this->solverDivide = solverDivide; } + + /** \brief Set the minimum number of sample points that should fall within an octree node as the octree + * construction is adapted to sampling density + * \note For noise-free samples, small values in the range [1.0 - 5.0] can be used. For more noisy samples, + * larger values in the range [15.0 - 20.0] may be needed to provide a smoother, noise-reduced, reconstruction. + * \param[in] samples_per_node the given parameter value + */ + inline void + setSamplesPerNode(float samplesPerNode) { this->samplesPerNode = samplesPerNode; } + +private: + const Points::PointKernel& myPoints; + Mesh::MeshObject& myMesh; + int depth; + int solverDivide; + float samplesPerNode; +}; + } // namespace Reen #endif // REEN_SURFACETRIANGULATION_H diff --git a/src/Mod/ReverseEngineering/Gui/CMakeLists.txt b/src/Mod/ReverseEngineering/Gui/CMakeLists.txt index bebb1a1c15..555b7ac1c8 100644 --- a/src/Mod/ReverseEngineering/Gui/CMakeLists.txt +++ b/src/Mod/ReverseEngineering/Gui/CMakeLists.txt @@ -27,12 +27,14 @@ qt4_add_resources(ReenGui_QRC_SRCS Resources/ReverseEngineering.qrc) set(ReenGui_MOC_HDRS FitBSplineSurface.h + Poisson.h ) fc_wrap_cpp(ReenGui_MOC_SRCS ${ReenGui_MOC_HDRS}) SOURCE_GROUP("Moc" FILES ${ReenGui_MOC_SRCS}) set(Dialogs_UIC_SRCS FitBSplineSurface.ui + Poisson.ui ) qt4_wrap_ui(Dialogs_UIC_HDRS ${Dialogs_UIC_SRCS}) SET(Dialogs_SRCS @@ -40,6 +42,8 @@ SET(Dialogs_SRCS ${Dialogs_UIC_SRCS} FitBSplineSurface.cpp FitBSplineSurface.h + Poisson.cpp + Poisson.h ) SOURCE_GROUP("Dialogs" FILES ${Dialogs_SRCS}) diff --git a/src/Mod/ReverseEngineering/Gui/Command.cpp b/src/Mod/ReverseEngineering/Gui/Command.cpp index b0f7e810a0..a0c3e2c675 100644 --- a/src/Mod/ReverseEngineering/Gui/Command.cpp +++ b/src/Mod/ReverseEngineering/Gui/Command.cpp @@ -45,6 +45,7 @@ #include "../App/ApproxSurface.h" #include "FitBSplineSurface.h" +#include "Poisson.h" using namespace std; @@ -179,9 +180,44 @@ bool CmdApproxPlane::isActive(void) return false; } +DEF_STD_CMD_A(CmdPoissonReconstruction); + +CmdPoissonReconstruction::CmdPoissonReconstruction() + : Command("Reen_PoissonReconstruction") +{ + sAppModule = "Reen"; + sGroup = QT_TR_NOOP("Reverse Engineering"); + sMenuText = QT_TR_NOOP("Poisson..."); + sToolTipText = QT_TR_NOOP("Poisson surface reconstruction"); + sWhatsThis = "Reen_PoissonReconstruction"; + sStatusTip = sToolTipText; +} + +void CmdPoissonReconstruction::activated(int iMsg) +{ + App::DocumentObjectT objT; + std::vector obj = Gui::Selection().getObjectsOfType(Points::Feature::getClassTypeId()); + if (obj.size() != 1) { + QMessageBox::warning(Gui::getMainWindow(), + qApp->translate("Reen_ApproxSurface", "Wrong selection"), + qApp->translate("Reen_ApproxSurface", "Please select a single point cloud.") + ); + return; + } + + objT = obj.front(); + Gui::Control().showDialog(new ReenGui::TaskPoisson(objT)); +} + +bool CmdPoissonReconstruction::isActive(void) +{ + return (hasActiveDocument() && !Gui::Control().activeDialog()); +} + void CreateReverseEngineeringCommands(void) { Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdApproxSurface()); rcCmdMgr.addCommand(new CmdApproxPlane()); + rcCmdMgr.addCommand(new CmdPoissonReconstruction()); } diff --git a/src/Mod/ReverseEngineering/Gui/Poisson.cpp b/src/Mod/ReverseEngineering/Gui/Poisson.cpp new file mode 100644 index 0000000000..ba5c2c5681 --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/Poisson.cpp @@ -0,0 +1,149 @@ +/*************************************************************************** + * Copyright (c) 2015 Werner Mayer * + * * + * 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., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +#endif + +#include "ui_Poisson.h" +#include "Poisson.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +using namespace ReenGui; + +class PoissonWidget::Private +{ +public: + Ui_PoissonWidget ui; + App::DocumentObjectT obj; + Private() + { + } + ~Private() + { + } +}; + +/* TRANSLATOR ReenGui::PoissonWidget */ + +PoissonWidget::PoissonWidget(const App::DocumentObjectT& obj, QWidget* parent) + : d(new Private()) +{ + d->ui.setupUi(this); + d->obj = obj; +} + +PoissonWidget::~PoissonWidget() +{ + delete d; +} + +bool PoissonWidget::accept() +{ + try { + QString document = QString::fromStdString(d->obj.getDocumentPython()); + QString object = QString::fromStdString(d->obj.getObjectPython()); + + QString argument = QString::fromLatin1( + "Points=%1.Points, " + "OctreeDepth=%2, " + "SolverDivide=%3, " + "SamplesPerNode=%4" + ) + .arg(object) + .arg(d->ui.octreeDepth->value()) + .arg(d->ui.solverDivide->value()) + .arg(d->ui.samplesPerNode->value()) + ; + QString command = QString::fromLatin1("%1.addObject(\"Mesh::Feature\", \"Poisson\").Mesh = " + "ReverseEngineering.poissonReconstruction(%2)") + .arg(document) + .arg(argument) + ; + + Gui::WaitCursor wc; + Gui::Command::addModule(Gui::Command::App, "ReverseEngineering"); + Gui::Command::openCommand("Poisson reconstruction"); + Gui::Command::doCommand(Gui::Command::Doc, command.toLatin1()); + Gui::Command::commitCommand(); + Gui::Command::updateActive(); + } + catch (const Base::Exception& e) { + Gui::Command::abortCommand(); + QMessageBox::warning(this, tr("Input error"), QString::fromAscii(e.what())); + return false; + } + + return true; +} + +void PoissonWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) { + d->ui.retranslateUi(this); + } +} + + +/* TRANSLATOR ReenGui::TaskPoisson */ + +TaskPoisson::TaskPoisson(const App::DocumentObjectT& obj) +{ + widget = new PoissonWidget(obj); + taskbox = new Gui::TaskView::TaskBox( + Gui::BitmapFactory().pixmap("actions/FitSurface"), + widget->windowTitle(), true, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskPoisson::~TaskPoisson() +{ +} + +void TaskPoisson::open() +{ +} + +bool TaskPoisson::accept() +{ + return widget->accept(); +} + +#include "moc_Poisson.cpp" diff --git a/src/Mod/ReverseEngineering/Gui/Poisson.h b/src/Mod/ReverseEngineering/Gui/Poisson.h new file mode 100644 index 0000000000..8521c1de51 --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/Poisson.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (c) 2015 Werner Mayer * + * * + * 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., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef REENGUI_POISSON_H +#define REENGUI_POISSON_H + +#include +#include +#include + +namespace ReenGui { + +class PoissonWidget : public QWidget +{ + Q_OBJECT + +public: + PoissonWidget(const App::DocumentObjectT&, QWidget* parent = 0); + ~PoissonWidget(); + + bool accept(); + +private: + void changeEvent(QEvent *e); + +private: + class Private; + Private* d; +}; + +class TaskPoisson : public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskPoisson(const App::DocumentObjectT&); + ~TaskPoisson(); + +public: + void open(); + bool accept(); + + QDialogButtonBox::StandardButtons getStandardButtons() const + { return QDialogButtonBox::Ok|QDialogButtonBox::Cancel; } + +private: + PoissonWidget* widget; + Gui::TaskView::TaskBox* taskbox; +}; + +} //namespace ReenGui + +#endif // REENGUI_POISSON_H diff --git a/src/Mod/ReverseEngineering/Gui/Poisson.ui b/src/Mod/ReverseEngineering/Gui/Poisson.ui new file mode 100644 index 0000000000..57bced5911 --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/Poisson.ui @@ -0,0 +1,90 @@ + + + ReenGui::PoissonWidget + + + + 0 + 0 + 244 + 146 + + + + Poisson + + + + + + Parameters + + + + + + Octree depth + + + + + + + 4 + + + 10 + + + 8 + + + + + + + Solver divide + + + + + + + 1 + + + 20 + + + 8 + + + + + + + Samples per node + + + + + + + 1 + + + 1.000000000000000 + + + 50.000000000000000 + + + + + + + + + + + diff --git a/src/Mod/ReverseEngineering/Gui/Workbench.cpp b/src/Mod/ReverseEngineering/Gui/Workbench.cpp index 67322a7569..67add12474 100644 --- a/src/Mod/ReverseEngineering/Gui/Workbench.cpp +++ b/src/Mod/ReverseEngineering/Gui/Workbench.cpp @@ -55,7 +55,14 @@ Gui::MenuItem* Workbench::setupMenuBar() const Gui::MenuItem* reen = new Gui::MenuItem; root->insertItem(item, reen); reen->setCommand("&REEN"); - *reen << "Reen_ApproxPlane" << "Reen_ApproxSurface"; + *reen << "Reen_ApproxPlane" + << "Reen_ApproxSurface"; + + Gui::MenuItem *reconstruct = new Gui::MenuItem(); + reconstruct->setCommand("Surface reconstruction"); + *reconstruct << "Reen_PoissonReconstruction"; + *reen << reconstruct; + return root; }