FreeCAD
841 строка · 32.4 Кб
1/***************************************************************************
2* Copyright (c) 2008 Jürgen Riegel <juergen.riegel@web.de> *
3* *
4* This file is part of the FreeCAD CAx development system. *
5* *
6* This library is free software; you can redistribute it and/or *
7* modify it under the terms of the GNU Library General Public *
8* License as published by the Free Software Foundation; either *
9* version 2 of the License, or (at your option) any later version. *
10* *
11* This library is distributed in the hope that it will be useful, *
12* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14* GNU Library General Public License for more details. *
15* *
16* You should have received a copy of the GNU Library General Public *
17* License along with this library; see the file COPYING.LIB. If not, *
18* write to the Free Software Foundation, Inc., 59 Temple Place, *
19* Suite 330, Boston, MA 02111-1307, USA *
20* *
21***************************************************************************/
22
23#include "PreCompiled.h"24#ifndef _PreComp_25#include <Geom_BSplineSurface.hxx>26#include <TColgp_Array1OfPnt.hxx>27#endif28
29#include <Base/Console.h>30#include <Base/Converter.h>31#include <Base/GeometryPyCXX.h>32#include <Base/Interpreter.h>33#include <Base/PyWrapParseTupleAndKeywords.h>34#include <Mod/Mesh/App/MeshPy.h>35#include <Mod/Part/App/BSplineSurfacePy.h>36#include <Mod/Points/App/PointsPy.h>37#if defined(HAVE_PCL_FILTERS)38#include <pcl/filters/passthrough.h>39#include <pcl/filters/voxel_grid.h>40#include <pcl/point_types.h>41#endif42
43#include "ApproxSurface.h"44#include "BSplineFitting.h"45#include "RegionGrowing.h"46#include "SampleConsensus.h"47#include "Segmentation.h"48#include "SurfaceTriangulation.h"49
50// clang-format off
51/*
52Dependency of pcl components:
53common: none
54features: common, kdtree, octree, search, (range_image)
55filters: common, kdtree, octree, sample_consenus, search
56geometry: common
57io: common, octree
58kdtree: common
59keypoints: common, features, filters, kdtree, octree, search, (range_image)
60octree: common
61recognition: common, features, search
62registration: common, features, kdtree, sample_consensus
63sample_consensus: common
64search: common, kdtree, octree
65segmentation: common, kdtree, octree, sample_consensus, search
66surface: common, kdtree, octree, search
67*/
68
69using namespace Reen;70
71namespace Reen {72class Module : public Py::ExtensionModule<Module>73{
74public:75Module() : Py::ExtensionModule<Module>("ReverseEngineering")76{77add_keyword_method("approxSurface",&Module::approxSurface,78"approxSurface(Points, UDegree=3, VDegree=3, NbUPoles=6, NbVPoles=6,\n"79"Smooth=True, Weight=0.1, Grad=1.0, Bend=0.0, Curv=0.0\n"80"Iterations=5, Correction=True, PatchFactor=1.0, UVDirs=((ux, uy, uz), (vx, vy, vz)))\n\n"81"Points: the input data (e.g. a point cloud or mesh)\n"82"UDegree: the degree in u parametric direction\n"83"VDegree: the degree in v parametric direction\n"84"NbUPoles: the number of control points in u parametric direction\n"85"NbVPoles: the number of control points in v parametric direction\n"86"Smooth: use energy terms to create a smooth surface\n"87"Weight: weight of the energy terms altogether\n"88"Grad: weight of the gradient term\n"89"Bend: weight of the bending energy term\n"90"Curv: weight of the deviation of curvature term\n"91"Iterations: number of iterations\n"92"Correction: perform a parameter correction of each iteration step\n"93"PatchFactor: create an extended surface\n"94"UVDirs: set the u,v parameter directions as tuple of two vectors\n"95" If not set then they will be determined by computing a best-fit plane\n"96);97#if defined(HAVE_PCL_SURFACE)98add_keyword_method("triangulate",&Module::triangulate,99"triangulate(PointKernel,searchRadius[,mu=2.5])."100);101add_keyword_method("poissonReconstruction",&Module::poissonReconstruction,102"poissonReconstruction(PointKernel)."103);104add_keyword_method("viewTriangulation",&Module::viewTriangulation,105"viewTriangulation(PointKernel, width, height)."106);107add_keyword_method("gridProjection",&Module::gridProjection,108"gridProjection(PointKernel)."109);110add_keyword_method("marchingCubesRBF",&Module::marchingCubesRBF,111"marchingCubesRBF(PointKernel)."112);113add_keyword_method("marchingCubesHoppe",&Module::marchingCubesHoppe,114"marchingCubesHoppe(PointKernel)."115);116#endif117#if defined(HAVE_PCL_OPENNURBS)118add_keyword_method("fitBSpline",&Module::fitBSpline,119"fitBSpline(PointKernel)."120);121#endif122#if defined(HAVE_PCL_FILTERS)123add_keyword_method("filterVoxelGrid",&Module::filterVoxelGrid,124"filterVoxelGrid(dim)."125);126add_keyword_method("normalEstimation",&Module::normalEstimation,127"normalEstimation(Points,[KSearch=0, SearchRadius=0]) -> Normals\n"128"KSearch is an int and used to search the k-nearest neighbours in\n"129"the k-d tree. Alternatively, SearchRadius (a float) can be used\n"130"as spatial distance to determine the neighbours of a point\n"131"Example:\n"132"\n"133"import ReverseEngineering as Reen\n"134"pts=App.ActiveDocument.ActiveObject.Points\n"135"nor=Reen.normalEstimation(pts,KSearch=5)\n"136"\n"137"f=App.ActiveDocument.addObject('Points::FeaturePython','Normals')\n"138"f.addProperty('Points::PropertyNormalList','Normal')\n"139"f.Points=pts\n"140"f.Normal=nor\n"141"f.ViewObject.Proxy=0\n"142"f.ViewObject.DisplayMode=1\n"143);144#endif145#if defined(HAVE_PCL_SEGMENTATION)146add_keyword_method("regionGrowingSegmentation",&Module::regionGrowingSegmentation,147"regionGrowingSegmentation()."148);149add_keyword_method("featureSegmentation",&Module::featureSegmentation,150"featureSegmentation()."151);152#endif153#if defined(HAVE_PCL_SAMPLE_CONSENSUS)154add_keyword_method("sampleConsensus",&Module::sampleConsensus,155"sampleConsensus()."156);157#endif158initialize("This module is the ReverseEngineering module."); // register with Python159}160
161private:162Py::Object approxSurface(const Py::Tuple& args, const Py::Dict& kwds)163{164PyObject *o;165PyObject *uvdirs = nullptr;166// spline parameters167int uDegree = 3;168int vDegree = 3;169int uPoles = 6;170int vPoles = 6;171// smoothing172PyObject* smooth = Py_True;173double weight = 0.1;174double grad = 1.0; //0.5175double bend = 0.0; //0.2176double curv = 0.0; //0.3177// other parameters178int iteration = 5;179PyObject* correction = Py_True;180double factor = 1.0;181
182static const std::array<const char *, 15> kwds_approx{"Points", "UDegree", "VDegree", "NbUPoles", "NbVPoles",183"Smooth", "Weight", "Grad", "Bend", "Curv", "Iterations",184"Correction", "PatchFactor", "UVDirs", nullptr};185if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|iiiiO!ddddiO!dO!", kwds_approx,186&o, &uDegree, &vDegree, &uPoles, &vPoles,187&PyBool_Type, &smooth, &weight, &grad, &bend, &curv,188&iteration, &PyBool_Type, &correction, &factor,189&PyTuple_Type, &uvdirs)) {190throw Py::Exception();191}192
193int uOrder = uDegree + 1;194int vOrder = vDegree + 1;195
196// error checking197if (grad < 0.0 || grad > 1.0) {198throw Py::ValueError("Value of Grad out of range [0,1]");199}200if (bend < 0.0 || bend > 1.0) {201throw Py::ValueError("Value of Bend out of range [0,1]");202}203if (curv < 0.0 || curv > 1.0) {204throw Py::ValueError("Value of Curv out of range [0,1]");205}206if (uDegree < 1 || uOrder > uPoles) {207throw Py::ValueError("Value of uDegree out of range [1,NbUPoles-1]");208}209if (vDegree < 1 || vOrder > vPoles) {210throw Py::ValueError("Value of vDegree out of range [1,NbVPoles-1]");211}212
213double sum = (grad + bend + curv);214if (sum > 0)215weight = weight / sum;216
217try {218std::vector<Base::Vector3f> pts;219if (PyObject_TypeCheck(o, &(Points::PointsPy::Type))) {220Points::PointsPy* pPoints = static_cast<Points::PointsPy*>(o);221Points::PointKernel* points = pPoints->getPointKernelPtr();222pts = points->getBasicPoints();223}224else if (PyObject_TypeCheck(o, &(Mesh::MeshPy::Type))) {225const Mesh::MeshObject* mesh = static_cast<Mesh::MeshPy*>(o)->getMeshObjectPtr();226const MeshCore::MeshPointArray& points = mesh->getKernel().GetPoints();227pts.insert(pts.begin(), points.begin(), points.end());228}229else {230Py::Sequence l(o);231pts.reserve(l.size());232for (Py::Sequence::iterator it = l.begin(); it != l.end(); ++it) {233Py::Tuple t(*it);234pts.emplace_back(235Py::Float(t.getItem(0)),236Py::Float(t.getItem(1)),237Py::Float(t.getItem(2))238);239}240}241
242TColgp_Array1OfPnt clPoints(0, pts.size()-1);243if (clPoints.Length() < uPoles * vPoles) {244throw Py::ValueError("Too less data points for the specified number of poles");245}246
247int index=0;248for (const auto & pt : pts) {249clPoints(index++) = gp_Pnt(pt.x, pt.y, pt.z);250}251
252Reen::BSplineParameterCorrection pc(uOrder,vOrder,uPoles,vPoles);253Handle(Geom_BSplineSurface) hSurf;254
255if (uvdirs) {256Py::Tuple t(uvdirs);257Base::Vector3d u = Py::Vector(t.getItem(0)).toVector();258Base::Vector3d v = Py::Vector(t.getItem(1)).toVector();259pc.SetUV(u, v);260}261pc.EnableSmoothing(Base::asBoolean(smooth), weight, grad, bend, curv);262hSurf = pc.CreateSurface(clPoints, iteration, Base::asBoolean(correction), factor);263if (!hSurf.IsNull()) {264return Py::asObject(new Part::BSplineSurfacePy(new Part::GeomBSplineSurface(hSurf)));265}266
267throw Py::RuntimeError("Computation of B-spline surface failed");268}269catch (const Py::Exception&) {270// re-throw271throw;272}273catch (Standard_Failure &e) {274std::string str;275Standard_CString msg = e.GetMessageString();276str += typeid(e).name();277str += " ";278if (msg) {str += msg;}279else {str += "No OCCT Exception Message";}280throw Py::RuntimeError(str);281}282catch (const Base::Exception &e) {283throw Py::RuntimeError(e.what());284}285catch (...) {286throw Py::RuntimeError("Unknown C++ exception");287}288}289#if defined(HAVE_PCL_SURFACE)290/*291import ReverseEngineering as Reen
292import Points
293import Mesh
294import random
295
296r=random.Random()
297
298p=Points.Points()
299pts=[]
300for i in range(21):
301for j in range(21):
302pts.append(App.Vector(i,j,r.gauss(5,0.05)))
303
304p.addPoints(pts)
305m=Reen.triangulate(Points=p,SearchRadius=2.2)
306Mesh.show(m)
307*/
308Py::Object triangulate(const Py::Tuple& args, const Py::Dict& kwds)309{310PyObject *pts;311double searchRadius;312PyObject *vec = 0;313int ksearch=5;314double mu=2.5;315
316static const std::array<const char*,6> kwds_greedy {"Points", "SearchRadius", "Mu", "KSearch",317"Normals", NULL};318if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!d|diO", kwds_greedy,319&(Points::PointsPy::Type), &pts,320&searchRadius, &mu, &ksearch, &vec))321throw Py::Exception();322
323Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();324
325Mesh::MeshObject* mesh = new Mesh::MeshObject();326SurfaceTriangulation tria(*points, *mesh);327tria.setMu(mu);328tria.setSearchRadius(searchRadius);329if (vec) {330Py::Sequence list(vec);331std::vector<Base::Vector3f> normals;332normals.reserve(list.size());333for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {334Base::Vector3d v = Py::Vector(*it).toVector();335normals.push_back(Base::convertTo<Base::Vector3f>(v));336}337tria.perform(normals);338}339else {340tria.perform(ksearch);341}342
343return Py::asObject(new Mesh::MeshPy(mesh));344}345Py::Object poissonReconstruction(const Py::Tuple& args, const Py::Dict& kwds)346{347PyObject *pts;348PyObject *vec = 0;349int ksearch=5;350int octreeDepth=-1;351int solverDivide=-1;352double samplesPerNode=-1.0;353
354static const std::array<const char*,7> kwds_poisson {"Points", "KSearch", "OctreeDepth", "SolverDivide",355"SamplesPerNode", "Normals", NULL};356if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iiidO", kwds_poisson,357&(Points::PointsPy::Type), &pts,358&ksearch, &octreeDepth, &solverDivide, &samplesPerNode, &vec))359throw Py::Exception();360
361Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();362
363Mesh::MeshObject* mesh = new Mesh::MeshObject();364Reen::PoissonReconstruction poisson(*points, *mesh);365poisson.setDepth(octreeDepth);366poisson.setSolverDivide(solverDivide);367poisson.setSamplesPerNode(samplesPerNode);368if (vec) {369Py::Sequence list(vec);370std::vector<Base::Vector3f> normals;371normals.reserve(list.size());372for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {373Base::Vector3d v = Py::Vector(*it).toVector();374normals.push_back(Base::convertTo<Base::Vector3f>(v));375}376poisson.perform(normals);377}378else {379poisson.perform(ksearch);380}381
382return Py::asObject(new Mesh::MeshPy(mesh));383}384/*385import ReverseEngineering as Reen
386import Points
387import Mesh
388import random
389import math
390r=random.Random()
391p=Points.Points()
392pts=[]
393for i in range(21):
394for j in range(21):
395pts.append(App.Vector(i,j,r.random()))
396p.addPoints(pts)
397m=Reen.viewTriangulation(p,21,21)
398Mesh.show(m)
399def boxmueller():
400r1,r2=random.random(),random.random()
401return math.sqrt(-2*math.log(r1))*math.cos(2*math.pi*r2)
402p=Points.Points()
403pts=[]
404for i in range(21):
405for j in range(21):
406pts.append(App.Vector(i,j,r.gauss(5,0.05)))
407p.addPoints(pts)
408m=Reen.viewTriangulation(p,21,21)
409Mesh.show(m)
410*/
411Py::Object viewTriangulation(const Py::Tuple& args, const Py::Dict& kwds)412{413PyObject *pts;414int width;415int height;416
417static const std::array<const char*,4> kwds_view {"Points", "Width", "Height", NULL};418if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|ii", kwds_view,419&(Points::PointsPy::Type), &pts,420&width, &height))421throw Py::Exception();422
423Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();424
425try {426Mesh::MeshObject* mesh = new Mesh::MeshObject();427ImageTriangulation view(width, height, *points, *mesh);428view.perform();429
430return Py::asObject(new Mesh::MeshPy(mesh));431}432catch (const Base::Exception& e) {433throw Py::RuntimeError(e.what());434}435}436Py::Object gridProjection(const Py::Tuple& args, const Py::Dict& kwds)437{438PyObject *pts;439PyObject *vec = 0;440int ksearch=5;441
442static const std::array<const char*,4> kwds_greedy {"Points", "KSearch", "Normals", NULL};443if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_greedy,444&(Points::PointsPy::Type), &pts,445&ksearch, &vec))446throw Py::Exception();447
448Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();449
450Mesh::MeshObject* mesh = new Mesh::MeshObject();451GridReconstruction tria(*points, *mesh);452if (vec) {453Py::Sequence list(vec);454std::vector<Base::Vector3f> normals;455normals.reserve(list.size());456for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {457Base::Vector3d v = Py::Vector(*it).toVector();458normals.push_back(Base::convertTo<Base::Vector3f>(v));459}460tria.perform(normals);461}462else {463tria.perform(ksearch);464}465
466return Py::asObject(new Mesh::MeshPy(mesh));467}468Py::Object marchingCubesRBF(const Py::Tuple& args, const Py::Dict& kwds)469{470PyObject *pts;471PyObject *vec = 0;472int ksearch=5;473
474static const std::array<const char*,4> kwds_greedy {"Points", "KSearch", "Normals", NULL};475if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_greedy,476&(Points::PointsPy::Type), &pts,477&ksearch, &vec))478throw Py::Exception();479
480Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();481
482Mesh::MeshObject* mesh = new Mesh::MeshObject();483MarchingCubesRBF tria(*points, *mesh);484if (vec) {485Py::Sequence list(vec);486std::vector<Base::Vector3f> normals;487normals.reserve(list.size());488for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {489Base::Vector3d v = Py::Vector(*it).toVector();490normals.push_back(Base::convertTo<Base::Vector3f>(v));491}492tria.perform(normals);493}494else {495tria.perform(ksearch);496}497
498return Py::asObject(new Mesh::MeshPy(mesh));499}500/*
501import ReverseEngineering as Reen
502import Points
503import Mesh
504import random
505r=random.Random()
506p=Points.Points()
507pts=[]
508for i in range(21):
509for j in range(21):
510pts.append(App.Vector(i,j,r.gauss(5,0.05)))
511p.addPoints(pts)
512m=Reen.marchingCubesHoppe(Points=p)
513Mesh.show(m)
514*/
515Py::Object marchingCubesHoppe(const Py::Tuple& args, const Py::Dict& kwds)516{517PyObject *pts;518PyObject *vec = 0;519int ksearch=5;520
521static const std::array<const char*,4> kwds_greedy {"Points", "KSearch", "Normals", NULL};522if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_greedy,523&(Points::PointsPy::Type), &pts,524&ksearch, &vec))525throw Py::Exception();526
527Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();528
529Mesh::MeshObject* mesh = new Mesh::MeshObject();530MarchingCubesHoppe tria(*points, *mesh);531if (vec) {532Py::Sequence list(vec);533std::vector<Base::Vector3f> normals;534normals.reserve(list.size());535for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {536Base::Vector3d v = Py::Vector(*it).toVector();537normals.push_back(Base::convertTo<Base::Vector3f>(v));538}539tria.perform(normals);540}541else {542tria.perform(ksearch);543}544
545return Py::asObject(new Mesh::MeshPy(mesh));546}547#endif548#if defined(HAVE_PCL_OPENNURBS)549Py::Object fitBSpline(const Py::Tuple& args, const Py::Dict& kwds)550{551PyObject *pts;552int degree = 2;553int refinement = 4;554int iterations = 10;555double interiorSmoothness = 0.2;556double interiorWeight = 1.0;557double boundarySmoothness = 0.2;558double boundaryWeight = 0.0;559
560static const std::array<const char*,9> kwds_approx {"Points", "Degree", "Refinement", "Iterations",561"InteriorSmoothness", "InteriorWeight", "BoundarySmoothness", "BoundaryWeight", NULL};562if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iiidddd", kwds_approx,563&(Points::PointsPy::Type), &pts,564°ree, &refinement, &iterations,565&interiorSmoothness, &interiorWeight,566&boundarySmoothness, &boundaryWeight))567throw Py::Exception();568
569Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();570
571BSplineFitting fit(points->getBasicPoints());572fit.setOrder(degree+1);573fit.setRefinement(refinement);574fit.setIterations(iterations);575fit.setInteriorSmoothness(interiorSmoothness);576fit.setInteriorWeight(interiorWeight);577fit.setBoundarySmoothness(boundarySmoothness);578fit.setBoundaryWeight(boundaryWeight);579Handle(Geom_BSplineSurface) hSurf = fit.perform();580
581if (!hSurf.IsNull()) {582return Py::asObject(new Part::BSplineSurfacePy(new Part::GeomBSplineSurface(hSurf)));583}584
585throw Py::RuntimeError("Computation of B-spline surface failed");586}587#endif588#if defined(HAVE_PCL_FILTERS)589Py::Object filterVoxelGrid(const Py::Tuple& args, const Py::Dict& kwds)590{591PyObject *pts;592double voxDimX = 0;593double voxDimY = 0;594double voxDimZ = 0;595
596static const std::array<const char*,5> kwds_voxel {"Points", "DimX", "DimY", "DimZ", NULL};597if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!d|dd", kwds_voxel,598&(Points::PointsPy::Type), &pts,599&voxDimX, &voxDimY, &voxDimZ))600throw Py::Exception();601
602if (voxDimY == 0)603voxDimY = voxDimX;604
605if (voxDimZ == 0)606voxDimZ = voxDimX;607
608Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();609
610pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);611cloud->reserve(points->size());612for (Points::PointKernel::const_iterator it = points->begin(); it != points->end(); ++it) {613cloud->push_back(pcl::PointXYZ(it->x, it->y, it->z));614}615
616// Create the filtering object617pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downSmpl (new pcl::PointCloud<pcl::PointXYZ>);618pcl::VoxelGrid<pcl::PointXYZ> voxG;619voxG.setInputCloud (cloud);620voxG.setLeafSize (voxDimX, voxDimY, voxDimZ);621voxG.filter (*cloud_downSmpl);622
623Points::PointKernel* points_sample = new Points::PointKernel();624points_sample->reserve(cloud_downSmpl->size());625for (pcl::PointCloud<pcl::PointXYZ>::const_iterator it = cloud_downSmpl->begin();it!=cloud_downSmpl->end();++it) {626points_sample->push_back(Base::Vector3d(it->x,it->y,it->z));627}628
629return Py::asObject(new Points::PointsPy(points_sample));630}631#endif632#if defined(HAVE_PCL_FILTERS)633Py::Object normalEstimation(const Py::Tuple& args, const Py::Dict& kwds)634{635PyObject *pts;636int ksearch=0;637double searchRadius=0;638
639static const std::array<const char*,4> kwds_normals {"Points", "KSearch", "SearchRadius", NULL};640if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|id", kwds_normals,641&(Points::PointsPy::Type), &pts,642&ksearch, &searchRadius))643throw Py::Exception();644
645Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();646
647std::vector<Base::Vector3d> normals;648NormalEstimation estimate(*points);649estimate.setKSearch(ksearch);650estimate.setSearchRadius(searchRadius);651estimate.perform(normals);652
653Py::List list;654for (std::vector<Base::Vector3d>::iterator it = normals.begin(); it != normals.end(); ++it) {655list.append(Py::Vector(*it));656}657
658return list;659}660#endif661#if defined(HAVE_PCL_SEGMENTATION)662Py::Object regionGrowingSegmentation(const Py::Tuple& args, const Py::Dict& kwds)663{664PyObject *pts;665PyObject *vec = 0;666int ksearch=5;667
668static const std::array<const char*,4> kwds_segment {"Points", "KSearch", "Normals", NULL};669if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_segment,670&(Points::PointsPy::Type), &pts,671&ksearch, &vec))672throw Py::Exception();673
674Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();675
676std::list<std::vector<int> > clusters;677RegionGrowing segm(*points, clusters);678if (vec) {679Py::Sequence list(vec);680std::vector<Base::Vector3f> normals;681normals.reserve(list.size());682for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {683Base::Vector3d v = Py::Vector(*it).toVector();684normals.push_back(Base::convertTo<Base::Vector3f>(v));685}686segm.perform(normals);687}688else {689segm.perform(ksearch);690}691
692Py::List lists;693for (std::list<std::vector<int> >::iterator it = clusters.begin(); it != clusters.end(); ++it) {694Py::Tuple tuple(it->size());695for (std::size_t i = 0; i < it->size(); i++) {696tuple.setItem(i, Py::Long((*it)[i]));697}698lists.append(tuple);699}700
701return lists;702}703Py::Object featureSegmentation(const Py::Tuple& args, const Py::Dict& kwds)704{705PyObject *pts;706int ksearch=5;707
708static const std::array<const char*,3> kwds_segment {"Points", "KSearch", NULL};709if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|i", kwds_segment,710&(Points::PointsPy::Type), &pts, &ksearch))711throw Py::Exception();712
713Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();714
715std::list<std::vector<int> > clusters;716Segmentation segm(*points, clusters);717segm.perform(ksearch);718
719Py::List lists;720for (std::list<std::vector<int> >::iterator it = clusters.begin(); it != clusters.end(); ++it) {721Py::Tuple tuple(it->size());722for (std::size_t i = 0; i < it->size(); i++) {723tuple.setItem(i, Py::Long((*it)[i]));724}725lists.append(tuple);726}727
728return lists;729}730#endif731#if defined(HAVE_PCL_SAMPLE_CONSENSUS)732/*
733import ReverseEngineering as reen
734import Points
735import Part
736p = App.ActiveDocument.Points.Points
737data = p.Points
738n = reen.normalEstimation(p, 10)
739model = reen.sampleConsensus(SacModel="Plane", Points=p)
740indices = model["Model"]
741param = model["Parameters"]
742plane = Part.Plane()
743plane.Axis = param[0:3]
744plane.Position = -plane.Axis * param[3]
745np = Points.Points()
746np.addPoints([data[i] for i in indices])
747Points.show(np)
748# sort in descending order
749indices = list(indices)
750indices.sort(reverse=True)
751# remove points of segment
752for i in indices:
753del data[i]
754del n[i]
755p = Points.Points()
756p.addPoints(data)
757model = reen.sampleConsensus(SacModel="Cylinder", Points=p, Normals=n)
758indices = model["Model"]
759np = Points.Points()
760np.addPoints([data[i] for i in indices])
761Points.show(np)
762*/
763Py::Object sampleConsensus(const Py::Tuple& args, const Py::Dict& kwds)764{765PyObject *pts;766PyObject *vec = nullptr;767const char* sacModelType = nullptr;768
769static const std::array<const char*,4> kwds_sample {"SacModel", "Points", "Normals", NULL};770if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "sO!|O", kwds_sample,771&sacModelType, &(Points::PointsPy::Type), &pts, &vec))772throw Py::Exception();773
774Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();775std::vector<Base::Vector3d> normals;776if (vec) {777Py::Sequence list(vec);778normals.reserve(list.size());779for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {780Base::Vector3d v = Py::Vector(*it).toVector();781normals.push_back(v);782}783}784
785SampleConsensus::SacModel sacModel = SampleConsensus::SACMODEL_PLANE;786if (sacModelType) {787if (strcmp(sacModelType, "Cylinder") == 0)788sacModel = SampleConsensus::SACMODEL_CYLINDER;789else if (strcmp(sacModelType, "Sphere") == 0)790sacModel = SampleConsensus::SACMODEL_SPHERE;791else if (strcmp(sacModelType, "Cone") == 0)792sacModel = SampleConsensus::SACMODEL_CONE;793}794
795std::vector<float> parameters;796SampleConsensus sample(sacModel, *points, normals);797std::vector<int> model;798double probability = sample.perform(parameters, model);799
800Py::Dict dict;801Py::Tuple tuple(parameters.size());802for (std::size_t i = 0; i < parameters.size(); i++)803tuple.setItem(i, Py::Float(parameters[i]));804Py::Tuple data(model.size());805for (std::size_t i = 0; i < model.size(); i++)806data.setItem(i, Py::Long(model[i]));807dict.setItem(Py::String("Probability"), Py::Float(probability));808dict.setItem(Py::String("Parameters"), tuple);809dict.setItem(Py::String("Model"), data);810
811return dict;812}813#endif814};815
816PyObject* initModule()817{
818return Base::Interpreter().addModule(new Module);819}
820
821} // namespace Reen822
823
824/* Python entry */
825PyMOD_INIT_FUNC(ReverseEngineering)826{
827// load dependent module828try {829Base::Interpreter().loadModule("Part");830Base::Interpreter().loadModule("Mesh");831}832catch(const Base::Exception& e) {833PyErr_SetString(PyExc_ImportError, e.what());834PyMOD_Return(nullptr);835}836
837PyObject* mod = Reen::initModule();838Base::Console().Log("Loading ReverseEngineering module... done\n");839PyMOD_Return(mod);840}
841// clang-format on
842