FreeCAD
1005 строк · 39.1 Кб
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#endif
28
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#endif
42
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("approxCurve", &Module::approxCurve, "Approximate curve");
78add_keyword_method("approxSurface",&Module::approxSurface,
79"approxSurface(Points, UDegree=3, VDegree=3, NbUPoles=6, NbVPoles=6,\n"
80"Smooth=True, Weight=0.1, Grad=1.0, Bend=0.0, Curv=0.0\n"
81"Iterations=5, Correction=True, PatchFactor=1.0, UVDirs=((ux, uy, uz), (vx, vy, vz)))\n\n"
82"Points: the input data (e.g. a point cloud or mesh)\n"
83"UDegree: the degree in u parametric direction\n"
84"VDegree: the degree in v parametric direction\n"
85"NbUPoles: the number of control points in u parametric direction\n"
86"NbVPoles: the number of control points in v parametric direction\n"
87"Smooth: use energy terms to create a smooth surface\n"
88"Weight: weight of the energy terms altogether\n"
89"Grad: weight of the gradient term\n"
90"Bend: weight of the bending energy term\n"
91"Curv: weight of the deviation of curvature term\n"
92"Iterations: number of iterations\n"
93"Correction: perform a parameter correction of each iteration step\n"
94"PatchFactor: create an extended surface\n"
95"UVDirs: set the u,v parameter directions as tuple of two vectors\n"
96" If not set then they will be determined by computing a best-fit plane\n"
97);
98#if defined(HAVE_PCL_SURFACE)
99add_keyword_method("triangulate",&Module::triangulate,
100"triangulate(PointKernel,searchRadius[,mu=2.5])."
101);
102add_keyword_method("poissonReconstruction",&Module::poissonReconstruction,
103"poissonReconstruction(PointKernel)."
104);
105add_keyword_method("viewTriangulation",&Module::viewTriangulation,
106"viewTriangulation(PointKernel, width, height)."
107);
108add_keyword_method("gridProjection",&Module::gridProjection,
109"gridProjection(PointKernel)."
110);
111add_keyword_method("marchingCubesRBF",&Module::marchingCubesRBF,
112"marchingCubesRBF(PointKernel)."
113);
114add_keyword_method("marchingCubesHoppe",&Module::marchingCubesHoppe,
115"marchingCubesHoppe(PointKernel)."
116);
117#endif
118#if defined(HAVE_PCL_OPENNURBS)
119add_keyword_method("fitBSpline",&Module::fitBSpline,
120"fitBSpline(PointKernel)."
121);
122#endif
123#if defined(HAVE_PCL_FILTERS)
124add_keyword_method("filterVoxelGrid",&Module::filterVoxelGrid,
125"filterVoxelGrid(dim)."
126);
127add_keyword_method("normalEstimation",&Module::normalEstimation,
128"normalEstimation(Points,[KSearch=0, SearchRadius=0]) -> Normals\n"
129"KSearch is an int and used to search the k-nearest neighbours in\n"
130"the k-d tree. Alternatively, SearchRadius (a float) can be used\n"
131"as spatial distance to determine the neighbours of a point\n"
132"Example:\n"
133"\n"
134"import ReverseEngineering as Reen\n"
135"pts=App.ActiveDocument.ActiveObject.Points\n"
136"nor=Reen.normalEstimation(pts,KSearch=5)\n"
137"\n"
138"f=App.ActiveDocument.addObject('Points::FeaturePython','Normals')\n"
139"f.addProperty('Points::PropertyNormalList','Normal')\n"
140"f.Points=pts\n"
141"f.Normal=nor\n"
142"f.ViewObject.Proxy=0\n"
143"f.ViewObject.DisplayMode=1\n"
144);
145#endif
146#if defined(HAVE_PCL_SEGMENTATION)
147add_keyword_method("regionGrowingSegmentation",&Module::regionGrowingSegmentation,
148"regionGrowingSegmentation()."
149);
150add_keyword_method("featureSegmentation",&Module::featureSegmentation,
151"featureSegmentation()."
152);
153#endif
154#if defined(HAVE_PCL_SAMPLE_CONSENSUS)
155add_keyword_method("sampleConsensus",&Module::sampleConsensus,
156"sampleConsensus()."
157);
158#endif
159initialize("This module is the ReverseEngineering module."); // register with Python
160}
161
162private:
163static std::vector<Base::Vector3d> getPoints(PyObject* pts, bool closed)
164{
165std::vector<Base::Vector3d> data;
166if (PyObject_TypeCheck(pts, &(Points::PointsPy::Type))) {
167std::vector<Base::Vector3d> normal;
168auto pypts = static_cast<Points::PointsPy*>(pts);
169Points::PointKernel* points = pypts->getPointKernelPtr();
170points->getPoints(data, normal, 0.0);
171}
172else {
173Py::Sequence l(pts);
174data.reserve(l.size());
175for (Py::Sequence::iterator it = l.begin(); it != l.end(); ++it) {
176Py::Tuple t(*it);
177data.emplace_back(
178Py::Float(t.getItem(0)),
179Py::Float(t.getItem(1)),
180Py::Float(t.getItem(2))
181);
182}
183}
184
185if (closed) {
186if (!data.empty()) {
187data.push_back(data.front());
188}
189}
190
191return data;
192}
193
194static PyObject* approx1(const Py::Tuple& args, const Py::Dict& kwds)
195{
196PyObject* pts {};
197PyObject* closed = Py_False;
198int minDegree = 3; // NOLINT
199int maxDegree = 8; // NOLINT
200int cont = int(GeomAbs_C2);
201double tol3d = 1.0e-3; // NOLINT
202
203static const std::array<const char *, 7> kwds_approx{"Points",
204"Closed",
205"MinDegree",
206"MaxDegree",
207"Continuity",
208"Tolerance",
209nullptr};
210if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!iiid", kwds_approx,
211&pts, &PyBool_Type, &closed, &minDegree,
212&maxDegree, &cont, &tol3d)) {
213return nullptr;
214}
215
216std::vector<Base::Vector3d> data = getPoints(pts, Base::asBoolean(closed));
217
218Part::GeomBSplineCurve curve;
219curve.approximate(data, minDegree, maxDegree, GeomAbs_Shape(cont), tol3d);
220return curve.getPyObject();
221}
222
223static PyObject* approx2(const Py::Tuple& args, const Py::Dict& kwds)
224{
225PyObject* pts {};
226char* parType {};
227PyObject* closed = Py_False;
228int minDegree = 3; // NOLINT
229int maxDegree = 8; // NOLINT
230int cont = int(GeomAbs_C2);
231double tol3d = 1.0e-3; // NOLINT
232
233static const std::array<const char *, 8> kwds_approx{"Points",
234"ParametrizationType",
235"Closed",
236"MinDegree",
237"MaxDegree",
238"Continuity",
239"Tolerance",
240nullptr};
241if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "Os|O!iiid", kwds_approx,
242&pts, &parType, &PyBool_Type, &closed, &minDegree,
243&maxDegree, &cont, &tol3d)) {
244return nullptr;
245}
246
247std::vector<Base::Vector3d> data = getPoints(pts, Base::asBoolean(closed));
248
249Approx_ParametrizationType pt {Approx_ChordLength};
250std::string pstr = parType;
251if (pstr == "Uniform") {
252pt = Approx_IsoParametric;
253}
254else if (pstr == "Centripetal") {
255pt = Approx_Centripetal;
256}
257
258Part::GeomBSplineCurve curve;
259curve.approximate(data, pt, minDegree, maxDegree, GeomAbs_Shape(cont), tol3d);
260return curve.getPyObject();
261}
262
263static PyObject* approx3(const Py::Tuple& args, const Py::Dict& kwds)
264{
265PyObject* pts {};
266double weight1 {};
267double weight2 {};
268double weight3 {};
269PyObject* closed = Py_False;
270int maxDegree = 8; // NOLINT
271int cont = int(GeomAbs_C2);
272double tol3d = 1.0e-3; // NOLINT
273
274static const std::array<const char *, 9> kwds_approx{"Points",
275"Weight1",
276"Weight2",
277"Weight3",
278"Closed",
279"MaxDegree",
280"Continuity",
281"Tolerance",
282nullptr};
283if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "Oddd|O!iid", kwds_approx,
284&pts, &weight1, &weight2, &weight3,
285&PyBool_Type, &closed,
286&maxDegree, &cont, &tol3d)) {
287return nullptr;
288}
289
290std::vector<Base::Vector3d> data = getPoints(pts, Base::asBoolean(closed));
291
292Part::GeomBSplineCurve curve;
293curve.approximate(data, weight1, weight2, weight3, maxDegree, GeomAbs_Shape(cont), tol3d);
294return curve.getPyObject();
295}
296
297Py::Object approxCurve(const Py::Tuple& args, const Py::Dict& kwds)
298{
299try {
300using approxFunc = std::function<PyObject*(const Py::Tuple& args, const Py::Dict& kwds)>;
301
302std::vector<approxFunc> funcs;
303funcs.emplace_back(approx3);
304funcs.emplace_back(approx2);
305funcs.emplace_back(approx1);
306
307for (const auto& func : funcs) {
308if (PyObject* py = func(args, kwds)) {
309return Py::asObject(py);
310}
311
312PyErr_Clear();
313}
314
315throw Py::ValueError("Wrong arguments ReverseEngineering.approxCurve()");
316}
317catch (const Base::Exception& e) {
318std::string msg = e.what();
319if (msg.empty()) {
320msg = "ReverseEngineering.approxCurve() failed";
321}
322throw Py::RuntimeError(msg);
323}
324}
325
326Py::Object approxSurface(const Py::Tuple& args, const Py::Dict& kwds)
327{
328PyObject *o;
329PyObject *uvdirs = nullptr;
330// spline parameters
331int uDegree = 3;
332int vDegree = 3;
333int uPoles = 6;
334int vPoles = 6;
335// smoothing
336PyObject* smooth = Py_True;
337double weight = 0.1;
338double grad = 1.0; //0.5
339double bend = 0.0; //0.2
340double curv = 0.0; //0.3
341// other parameters
342int iteration = 5;
343PyObject* correction = Py_True;
344double factor = 1.0;
345
346static const std::array<const char *, 15> kwds_approx{"Points", "UDegree", "VDegree", "NbUPoles", "NbVPoles",
347"Smooth", "Weight", "Grad", "Bend", "Curv", "Iterations",
348"Correction", "PatchFactor", "UVDirs", nullptr};
349if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|iiiiO!ddddiO!dO!", kwds_approx,
350&o, &uDegree, &vDegree, &uPoles, &vPoles,
351&PyBool_Type, &smooth, &weight, &grad, &bend, &curv,
352&iteration, &PyBool_Type, &correction, &factor,
353&PyTuple_Type, &uvdirs)) {
354throw Py::Exception();
355}
356
357int uOrder = uDegree + 1;
358int vOrder = vDegree + 1;
359
360// error checking
361if (grad < 0.0 || grad > 1.0) {
362throw Py::ValueError("Value of Grad out of range [0,1]");
363}
364if (bend < 0.0 || bend > 1.0) {
365throw Py::ValueError("Value of Bend out of range [0,1]");
366}
367if (curv < 0.0 || curv > 1.0) {
368throw Py::ValueError("Value of Curv out of range [0,1]");
369}
370if (uDegree < 1 || uOrder > uPoles) {
371throw Py::ValueError("Value of uDegree out of range [1,NbUPoles-1]");
372}
373if (vDegree < 1 || vOrder > vPoles) {
374throw Py::ValueError("Value of vDegree out of range [1,NbVPoles-1]");
375}
376
377double sum = (grad + bend + curv);
378if (sum > 0)
379weight = weight / sum;
380
381try {
382std::vector<Base::Vector3f> pts;
383if (PyObject_TypeCheck(o, &(Points::PointsPy::Type))) {
384Points::PointsPy* pPoints = static_cast<Points::PointsPy*>(o);
385Points::PointKernel* points = pPoints->getPointKernelPtr();
386pts = points->getBasicPoints();
387}
388else if (PyObject_TypeCheck(o, &(Mesh::MeshPy::Type))) {
389const Mesh::MeshObject* mesh = static_cast<Mesh::MeshPy*>(o)->getMeshObjectPtr();
390const MeshCore::MeshPointArray& points = mesh->getKernel().GetPoints();
391pts.insert(pts.begin(), points.begin(), points.end());
392}
393else {
394Py::Sequence l(o);
395pts.reserve(l.size());
396for (Py::Sequence::iterator it = l.begin(); it != l.end(); ++it) {
397Py::Tuple t(*it);
398pts.emplace_back(
399Py::Float(t.getItem(0)),
400Py::Float(t.getItem(1)),
401Py::Float(t.getItem(2))
402);
403}
404}
405
406TColgp_Array1OfPnt clPoints(0, pts.size()-1);
407if (clPoints.Length() < uPoles * vPoles) {
408throw Py::ValueError("Too less data points for the specified number of poles");
409}
410
411int index=0;
412for (const auto & pt : pts) {
413clPoints(index++) = gp_Pnt(pt.x, pt.y, pt.z);
414}
415
416Reen::BSplineParameterCorrection pc(uOrder,vOrder,uPoles,vPoles);
417Handle(Geom_BSplineSurface) hSurf;
418
419if (uvdirs) {
420Py::Tuple t(uvdirs);
421Base::Vector3d u = Py::Vector(t.getItem(0)).toVector();
422Base::Vector3d v = Py::Vector(t.getItem(1)).toVector();
423pc.SetUV(u, v);
424}
425pc.EnableSmoothing(Base::asBoolean(smooth), weight, grad, bend, curv);
426hSurf = pc.CreateSurface(clPoints, iteration, Base::asBoolean(correction), factor);
427if (!hSurf.IsNull()) {
428return Py::asObject(new Part::BSplineSurfacePy(new Part::GeomBSplineSurface(hSurf)));
429}
430
431throw Py::RuntimeError("Computation of B-spline surface failed");
432}
433catch (const Py::Exception&) {
434// re-throw
435throw;
436}
437catch (Standard_Failure &e) {
438std::string str;
439Standard_CString msg = e.GetMessageString();
440str += typeid(e).name();
441str += " ";
442if (msg) {str += msg;}
443else {str += "No OCCT Exception Message";}
444throw Py::RuntimeError(str);
445}
446catch (const Base::Exception &e) {
447throw Py::RuntimeError(e.what());
448}
449catch (...) {
450throw Py::RuntimeError("Unknown C++ exception");
451}
452}
453#if defined(HAVE_PCL_SURFACE)
454/*
455import ReverseEngineering as Reen
456import Points
457import Mesh
458import random
459
460r=random.Random()
461
462p=Points.Points()
463pts=[]
464for i in range(21):
465for j in range(21):
466pts.append(App.Vector(i,j,r.gauss(5,0.05)))
467
468p.addPoints(pts)
469m=Reen.triangulate(Points=p,SearchRadius=2.2)
470Mesh.show(m)
471*/
472Py::Object triangulate(const Py::Tuple& args, const Py::Dict& kwds)
473{
474PyObject *pts;
475double searchRadius;
476PyObject *vec = 0;
477int ksearch=5;
478double mu=2.5;
479
480static const std::array<const char*,6> kwds_greedy {"Points", "SearchRadius", "Mu", "KSearch",
481"Normals", NULL};
482if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!d|diO", kwds_greedy,
483&(Points::PointsPy::Type), &pts,
484&searchRadius, &mu, &ksearch, &vec))
485throw Py::Exception();
486
487Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
488
489Mesh::MeshObject* mesh = new Mesh::MeshObject();
490SurfaceTriangulation tria(*points, *mesh);
491tria.setMu(mu);
492tria.setSearchRadius(searchRadius);
493if (vec) {
494Py::Sequence list(vec);
495std::vector<Base::Vector3f> normals;
496normals.reserve(list.size());
497for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
498Base::Vector3d v = Py::Vector(*it).toVector();
499normals.push_back(Base::convertTo<Base::Vector3f>(v));
500}
501tria.perform(normals);
502}
503else {
504tria.perform(ksearch);
505}
506
507return Py::asObject(new Mesh::MeshPy(mesh));
508}
509Py::Object poissonReconstruction(const Py::Tuple& args, const Py::Dict& kwds)
510{
511PyObject *pts;
512PyObject *vec = 0;
513int ksearch=5;
514int octreeDepth=-1;
515int solverDivide=-1;
516double samplesPerNode=-1.0;
517
518static const std::array<const char*,7> kwds_poisson {"Points", "KSearch", "OctreeDepth", "SolverDivide",
519"SamplesPerNode", "Normals", NULL};
520if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iiidO", kwds_poisson,
521&(Points::PointsPy::Type), &pts,
522&ksearch, &octreeDepth, &solverDivide, &samplesPerNode, &vec))
523throw Py::Exception();
524
525Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
526
527Mesh::MeshObject* mesh = new Mesh::MeshObject();
528Reen::PoissonReconstruction poisson(*points, *mesh);
529poisson.setDepth(octreeDepth);
530poisson.setSolverDivide(solverDivide);
531poisson.setSamplesPerNode(samplesPerNode);
532if (vec) {
533Py::Sequence list(vec);
534std::vector<Base::Vector3f> normals;
535normals.reserve(list.size());
536for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
537Base::Vector3d v = Py::Vector(*it).toVector();
538normals.push_back(Base::convertTo<Base::Vector3f>(v));
539}
540poisson.perform(normals);
541}
542else {
543poisson.perform(ksearch);
544}
545
546return Py::asObject(new Mesh::MeshPy(mesh));
547}
548/*
549import ReverseEngineering as Reen
550import Points
551import Mesh
552import random
553import math
554r=random.Random()
555p=Points.Points()
556pts=[]
557for i in range(21):
558for j in range(21):
559pts.append(App.Vector(i,j,r.random()))
560p.addPoints(pts)
561m=Reen.viewTriangulation(p,21,21)
562Mesh.show(m)
563def boxmueller():
564r1,r2=random.random(),random.random()
565return math.sqrt(-2*math.log(r1))*math.cos(2*math.pi*r2)
566p=Points.Points()
567pts=[]
568for i in range(21):
569for j in range(21):
570pts.append(App.Vector(i,j,r.gauss(5,0.05)))
571p.addPoints(pts)
572m=Reen.viewTriangulation(p,21,21)
573Mesh.show(m)
574*/
575Py::Object viewTriangulation(const Py::Tuple& args, const Py::Dict& kwds)
576{
577PyObject *pts;
578int width;
579int height;
580
581static const std::array<const char*,4> kwds_view {"Points", "Width", "Height", NULL};
582if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|ii", kwds_view,
583&(Points::PointsPy::Type), &pts,
584&width, &height))
585throw Py::Exception();
586
587Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
588
589try {
590Mesh::MeshObject* mesh = new Mesh::MeshObject();
591ImageTriangulation view(width, height, *points, *mesh);
592view.perform();
593
594return Py::asObject(new Mesh::MeshPy(mesh));
595}
596catch (const Base::Exception& e) {
597throw Py::RuntimeError(e.what());
598}
599}
600Py::Object gridProjection(const Py::Tuple& args, const Py::Dict& kwds)
601{
602PyObject *pts;
603PyObject *vec = 0;
604int ksearch=5;
605
606static const std::array<const char*,4> kwds_greedy {"Points", "KSearch", "Normals", NULL};
607if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_greedy,
608&(Points::PointsPy::Type), &pts,
609&ksearch, &vec))
610throw Py::Exception();
611
612Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
613
614Mesh::MeshObject* mesh = new Mesh::MeshObject();
615GridReconstruction tria(*points, *mesh);
616if (vec) {
617Py::Sequence list(vec);
618std::vector<Base::Vector3f> normals;
619normals.reserve(list.size());
620for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
621Base::Vector3d v = Py::Vector(*it).toVector();
622normals.push_back(Base::convertTo<Base::Vector3f>(v));
623}
624tria.perform(normals);
625}
626else {
627tria.perform(ksearch);
628}
629
630return Py::asObject(new Mesh::MeshPy(mesh));
631}
632Py::Object marchingCubesRBF(const Py::Tuple& args, const Py::Dict& kwds)
633{
634PyObject *pts;
635PyObject *vec = 0;
636int ksearch=5;
637
638static const std::array<const char*,4> kwds_greedy {"Points", "KSearch", "Normals", NULL};
639if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_greedy,
640&(Points::PointsPy::Type), &pts,
641&ksearch, &vec))
642throw Py::Exception();
643
644Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
645
646Mesh::MeshObject* mesh = new Mesh::MeshObject();
647MarchingCubesRBF tria(*points, *mesh);
648if (vec) {
649Py::Sequence list(vec);
650std::vector<Base::Vector3f> normals;
651normals.reserve(list.size());
652for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
653Base::Vector3d v = Py::Vector(*it).toVector();
654normals.push_back(Base::convertTo<Base::Vector3f>(v));
655}
656tria.perform(normals);
657}
658else {
659tria.perform(ksearch);
660}
661
662return Py::asObject(new Mesh::MeshPy(mesh));
663}
664/*
665import ReverseEngineering as Reen
666import Points
667import Mesh
668import random
669r=random.Random()
670p=Points.Points()
671pts=[]
672for i in range(21):
673for j in range(21):
674pts.append(App.Vector(i,j,r.gauss(5,0.05)))
675p.addPoints(pts)
676m=Reen.marchingCubesHoppe(Points=p)
677Mesh.show(m)
678*/
679Py::Object marchingCubesHoppe(const Py::Tuple& args, const Py::Dict& kwds)
680{
681PyObject *pts;
682PyObject *vec = 0;
683int ksearch=5;
684
685static const std::array<const char*,4> kwds_greedy {"Points", "KSearch", "Normals", NULL};
686if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_greedy,
687&(Points::PointsPy::Type), &pts,
688&ksearch, &vec))
689throw Py::Exception();
690
691Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
692
693Mesh::MeshObject* mesh = new Mesh::MeshObject();
694MarchingCubesHoppe tria(*points, *mesh);
695if (vec) {
696Py::Sequence list(vec);
697std::vector<Base::Vector3f> normals;
698normals.reserve(list.size());
699for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
700Base::Vector3d v = Py::Vector(*it).toVector();
701normals.push_back(Base::convertTo<Base::Vector3f>(v));
702}
703tria.perform(normals);
704}
705else {
706tria.perform(ksearch);
707}
708
709return Py::asObject(new Mesh::MeshPy(mesh));
710}
711#endif
712#if defined(HAVE_PCL_OPENNURBS)
713Py::Object fitBSpline(const Py::Tuple& args, const Py::Dict& kwds)
714{
715PyObject *pts;
716int degree = 2;
717int refinement = 4;
718int iterations = 10;
719double interiorSmoothness = 0.2;
720double interiorWeight = 1.0;
721double boundarySmoothness = 0.2;
722double boundaryWeight = 0.0;
723
724static const std::array<const char*,9> kwds_approx {"Points", "Degree", "Refinement", "Iterations",
725"InteriorSmoothness", "InteriorWeight", "BoundarySmoothness", "BoundaryWeight", NULL};
726if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iiidddd", kwds_approx,
727&(Points::PointsPy::Type), &pts,
728°ree, &refinement, &iterations,
729&interiorSmoothness, &interiorWeight,
730&boundarySmoothness, &boundaryWeight))
731throw Py::Exception();
732
733Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
734
735BSplineFitting fit(points->getBasicPoints());
736fit.setOrder(degree+1);
737fit.setRefinement(refinement);
738fit.setIterations(iterations);
739fit.setInteriorSmoothness(interiorSmoothness);
740fit.setInteriorWeight(interiorWeight);
741fit.setBoundarySmoothness(boundarySmoothness);
742fit.setBoundaryWeight(boundaryWeight);
743Handle(Geom_BSplineSurface) hSurf = fit.perform();
744
745if (!hSurf.IsNull()) {
746return Py::asObject(new Part::BSplineSurfacePy(new Part::GeomBSplineSurface(hSurf)));
747}
748
749throw Py::RuntimeError("Computation of B-spline surface failed");
750}
751#endif
752#if defined(HAVE_PCL_FILTERS)
753Py::Object filterVoxelGrid(const Py::Tuple& args, const Py::Dict& kwds)
754{
755PyObject *pts;
756double voxDimX = 0;
757double voxDimY = 0;
758double voxDimZ = 0;
759
760static const std::array<const char*,5> kwds_voxel {"Points", "DimX", "DimY", "DimZ", NULL};
761if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!d|dd", kwds_voxel,
762&(Points::PointsPy::Type), &pts,
763&voxDimX, &voxDimY, &voxDimZ))
764throw Py::Exception();
765
766if (voxDimY == 0)
767voxDimY = voxDimX;
768
769if (voxDimZ == 0)
770voxDimZ = voxDimX;
771
772Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
773
774pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
775cloud->reserve(points->size());
776for (Points::PointKernel::const_iterator it = points->begin(); it != points->end(); ++it) {
777cloud->push_back(pcl::PointXYZ(it->x, it->y, it->z));
778}
779
780// Create the filtering object
781pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downSmpl (new pcl::PointCloud<pcl::PointXYZ>);
782pcl::VoxelGrid<pcl::PointXYZ> voxG;
783voxG.setInputCloud (cloud);
784voxG.setLeafSize (voxDimX, voxDimY, voxDimZ);
785voxG.filter (*cloud_downSmpl);
786
787Points::PointKernel* points_sample = new Points::PointKernel();
788points_sample->reserve(cloud_downSmpl->size());
789for (pcl::PointCloud<pcl::PointXYZ>::const_iterator it = cloud_downSmpl->begin();it!=cloud_downSmpl->end();++it) {
790points_sample->push_back(Base::Vector3d(it->x,it->y,it->z));
791}
792
793return Py::asObject(new Points::PointsPy(points_sample));
794}
795#endif
796#if defined(HAVE_PCL_FILTERS)
797Py::Object normalEstimation(const Py::Tuple& args, const Py::Dict& kwds)
798{
799PyObject *pts;
800int ksearch=0;
801double searchRadius=0;
802
803static const std::array<const char*,4> kwds_normals {"Points", "KSearch", "SearchRadius", NULL};
804if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|id", kwds_normals,
805&(Points::PointsPy::Type), &pts,
806&ksearch, &searchRadius))
807throw Py::Exception();
808
809Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
810
811std::vector<Base::Vector3d> normals;
812NormalEstimation estimate(*points);
813estimate.setKSearch(ksearch);
814estimate.setSearchRadius(searchRadius);
815estimate.perform(normals);
816
817Py::List list;
818for (std::vector<Base::Vector3d>::iterator it = normals.begin(); it != normals.end(); ++it) {
819list.append(Py::Vector(*it));
820}
821
822return list;
823}
824#endif
825#if defined(HAVE_PCL_SEGMENTATION)
826Py::Object regionGrowingSegmentation(const Py::Tuple& args, const Py::Dict& kwds)
827{
828PyObject *pts;
829PyObject *vec = 0;
830int ksearch=5;
831
832static const std::array<const char*,4> kwds_segment {"Points", "KSearch", "Normals", NULL};
833if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|iO", kwds_segment,
834&(Points::PointsPy::Type), &pts,
835&ksearch, &vec))
836throw Py::Exception();
837
838Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
839
840std::list<std::vector<int> > clusters;
841RegionGrowing segm(*points, clusters);
842if (vec) {
843Py::Sequence list(vec);
844std::vector<Base::Vector3f> normals;
845normals.reserve(list.size());
846for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
847Base::Vector3d v = Py::Vector(*it).toVector();
848normals.push_back(Base::convertTo<Base::Vector3f>(v));
849}
850segm.perform(normals);
851}
852else {
853segm.perform(ksearch);
854}
855
856Py::List lists;
857for (std::list<std::vector<int> >::iterator it = clusters.begin(); it != clusters.end(); ++it) {
858Py::Tuple tuple(it->size());
859for (std::size_t i = 0; i < it->size(); i++) {
860tuple.setItem(i, Py::Long((*it)[i]));
861}
862lists.append(tuple);
863}
864
865return lists;
866}
867Py::Object featureSegmentation(const Py::Tuple& args, const Py::Dict& kwds)
868{
869PyObject *pts;
870int ksearch=5;
871
872static const std::array<const char*,3> kwds_segment {"Points", "KSearch", NULL};
873if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|i", kwds_segment,
874&(Points::PointsPy::Type), &pts, &ksearch))
875throw Py::Exception();
876
877Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
878
879std::list<std::vector<int> > clusters;
880Segmentation segm(*points, clusters);
881segm.perform(ksearch);
882
883Py::List lists;
884for (std::list<std::vector<int> >::iterator it = clusters.begin(); it != clusters.end(); ++it) {
885Py::Tuple tuple(it->size());
886for (std::size_t i = 0; i < it->size(); i++) {
887tuple.setItem(i, Py::Long((*it)[i]));
888}
889lists.append(tuple);
890}
891
892return lists;
893}
894#endif
895#if defined(HAVE_PCL_SAMPLE_CONSENSUS)
896/*
897import ReverseEngineering as reen
898import Points
899import Part
900p = App.ActiveDocument.Points.Points
901data = p.Points
902n = reen.normalEstimation(p, 10)
903model = reen.sampleConsensus(SacModel="Plane", Points=p)
904indices = model["Model"]
905param = model["Parameters"]
906plane = Part.Plane()
907plane.Axis = param[0:3]
908plane.Position = -plane.Axis * param[3]
909np = Points.Points()
910np.addPoints([data[i] for i in indices])
911Points.show(np)
912# sort in descending order
913indices = list(indices)
914indices.sort(reverse=True)
915# remove points of segment
916for i in indices:
917del data[i]
918del n[i]
919p = Points.Points()
920p.addPoints(data)
921model = reen.sampleConsensus(SacModel="Cylinder", Points=p, Normals=n)
922indices = model["Model"]
923np = Points.Points()
924np.addPoints([data[i] for i in indices])
925Points.show(np)
926*/
927Py::Object sampleConsensus(const Py::Tuple& args, const Py::Dict& kwds)
928{
929PyObject *pts;
930PyObject *vec = nullptr;
931const char* sacModelType = nullptr;
932
933static const std::array<const char*,4> kwds_sample {"SacModel", "Points", "Normals", NULL};
934if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "sO!|O", kwds_sample,
935&sacModelType, &(Points::PointsPy::Type), &pts, &vec))
936throw Py::Exception();
937
938Points::PointKernel* points = static_cast<Points::PointsPy*>(pts)->getPointKernelPtr();
939std::vector<Base::Vector3d> normals;
940if (vec) {
941Py::Sequence list(vec);
942normals.reserve(list.size());
943for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
944Base::Vector3d v = Py::Vector(*it).toVector();
945normals.push_back(v);
946}
947}
948
949SampleConsensus::SacModel sacModel = SampleConsensus::SACMODEL_PLANE;
950if (sacModelType) {
951if (strcmp(sacModelType, "Cylinder") == 0)
952sacModel = SampleConsensus::SACMODEL_CYLINDER;
953else if (strcmp(sacModelType, "Sphere") == 0)
954sacModel = SampleConsensus::SACMODEL_SPHERE;
955else if (strcmp(sacModelType, "Cone") == 0)
956sacModel = SampleConsensus::SACMODEL_CONE;
957}
958
959std::vector<float> parameters;
960SampleConsensus sample(sacModel, *points, normals);
961std::vector<int> model;
962double probability = sample.perform(parameters, model);
963
964Py::Dict dict;
965Py::Tuple tuple(parameters.size());
966for (std::size_t i = 0; i < parameters.size(); i++)
967tuple.setItem(i, Py::Float(parameters[i]));
968Py::Tuple data(model.size());
969for (std::size_t i = 0; i < model.size(); i++)
970data.setItem(i, Py::Long(model[i]));
971dict.setItem(Py::String("Probability"), Py::Float(probability));
972dict.setItem(Py::String("Parameters"), tuple);
973dict.setItem(Py::String("Model"), data);
974
975return dict;
976}
977#endif
978};
979
980PyObject* initModule()
981{
982return Base::Interpreter().addModule(new Module);
983}
984
985} // namespace Reen
986
987
988/* Python entry */
989PyMOD_INIT_FUNC(ReverseEngineering)
990{
991// load dependent module
992try {
993Base::Interpreter().loadModule("Part");
994Base::Interpreter().loadModule("Mesh");
995}
996catch(const Base::Exception& e) {
997PyErr_SetString(PyExc_ImportError, e.what());
998PyMOD_Return(nullptr);
999}
1000
1001PyObject* mod = Reen::initModule();
1002Base::Console().Log("Loading ReverseEngineering module... done\n");
1003PyMOD_Return(mod);
1004}
1005// clang-format on
1006