FreeCAD
996 строк · 31.1 Кб
1/***************************************************************************
2* Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> *
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
25#ifndef _PreComp_
26#include <boost/core/ignore_unused.hpp>
27#include <numeric>
28
29#include <BRepBuilderAPI_MakeVertex.hxx>
30#include <BRepClass3d_SolidClassifier.hxx>
31#include <BRepExtrema_DistShapeShape.hxx>
32#include <BRepGProp_Face.hxx>
33#include <TopExp.hxx>
34#include <TopoDS.hxx>
35#include <gp_Pnt.hxx>
36
37#include <QEventLoop>
38#include <QFuture>
39#include <QFutureWatcher>
40#include <QtConcurrentMap>
41#endif
42
43#include <Base/Console.h>
44#include <Base/FutureWatcherProgress.h>
45#include <Base/Sequencer.h>
46#include <Base/Stream.h>
47
48#include <Mod/Mesh/App/Core/Algorithm.h>
49#include <Mod/Mesh/App/Core/Grid.h>
50#include <Mod/Mesh/App/Core/Iterator.h>
51#include <Mod/Mesh/App/Core/MeshKernel.h>
52#include <Mod/Mesh/App/MeshFeature.h>
53#include <Mod/Part/App/PartFeature.h>
54#include <Mod/Points/App/PointsFeature.h>
55#include <Mod/Points/App/PointsGrid.h>
56
57#include "InspectionFeature.h"
58
59
60using namespace Inspection;
61namespace sp = std::placeholders;
62
63InspectActualMesh::InspectActualMesh(const Mesh::MeshObject& rMesh)
64: _mesh(rMesh.getKernel())
65{
66Base::Matrix4D tmp;
67_clTrf = rMesh.getTransform();
68_bApply = _clTrf != tmp;
69}
70
71InspectActualMesh::~InspectActualMesh() = default;
72
73unsigned long InspectActualMesh::countPoints() const
74{
75return _mesh.CountPoints();
76}
77
78Base::Vector3f InspectActualMesh::getPoint(unsigned long index) const
79{
80Base::Vector3f point = _mesh.GetPoint(index);
81if (_bApply) {
82_clTrf.multVec(point, point);
83}
84return point;
85}
86
87// ----------------------------------------------------------------
88
89InspectActualPoints::InspectActualPoints(const Points::PointKernel& rPoints)
90: _rKernel(rPoints)
91{}
92
93unsigned long InspectActualPoints::countPoints() const
94{
95return _rKernel.size();
96}
97
98Base::Vector3f InspectActualPoints::getPoint(unsigned long index) const
99{
100Base::Vector3d pnt = _rKernel.getPoint(index);
101return Base::Vector3f(float(pnt.x), float(pnt.y), float(pnt.z));
102}
103
104// ----------------------------------------------------------------
105
106InspectActualShape::InspectActualShape(const Part::TopoShape& shape)
107: _rShape(shape)
108{
109Standard_Real deflection = _rShape.getAccuracy();
110fetchPoints(deflection);
111}
112
113void InspectActualShape::fetchPoints(double deflection)
114{
115// get points from faces or sub-sampled edges
116TopTools_IndexedMapOfShape mapOfShapes;
117TopExp::MapShapes(_rShape.getShape(), TopAbs_FACE, mapOfShapes);
118if (!mapOfShapes.IsEmpty()) {
119std::vector<Data::ComplexGeoData::Facet> faces;
120_rShape.getFaces(points, faces, deflection);
121}
122else {
123TopExp::MapShapes(_rShape.getShape(), TopAbs_EDGE, mapOfShapes);
124if (!mapOfShapes.IsEmpty()) {
125std::vector<Data::ComplexGeoData::Line> lines;
126_rShape.getLines(points, lines, deflection);
127}
128else {
129std::vector<Base::Vector3d> normals;
130_rShape.getPoints(points, normals, deflection);
131}
132}
133}
134
135unsigned long InspectActualShape::countPoints() const
136{
137return points.size();
138}
139
140Base::Vector3f InspectActualShape::getPoint(unsigned long index) const
141{
142return Base::toVector<float>(points[index]);
143}
144
145// ----------------------------------------------------------------
146
147namespace Inspection
148{
149class MeshInspectGrid: public MeshCore::MeshGrid
150{
151public:
152MeshInspectGrid(const MeshCore::MeshKernel& mesh, float fGridLen, const Base::Matrix4D& mat)
153: MeshCore::MeshGrid(mesh)
154, _transform(mat)
155{
156Base::BoundBox3f clBBMesh = _pclMesh->GetBoundBox().Transformed(mat);
157Rebuild(std::max<unsigned long>((unsigned long)(clBBMesh.LengthX() / fGridLen), 1),
158std::max<unsigned long>((unsigned long)(clBBMesh.LengthY() / fGridLen), 1),
159std::max<unsigned long>((unsigned long)(clBBMesh.LengthZ() / fGridLen), 1));
160}
161
162void Validate(const MeshCore::MeshKernel& kernel) override
163{
164// do nothing
165boost::ignore_unused(kernel);
166}
167
168void Validate()
169{
170// do nothing
171}
172
173bool Verify() const override
174{
175// do nothing
176return true;
177}
178
179protected:
180void CalculateGridLength(unsigned long /*ulCtGrid*/, unsigned long /*ulMaxGrids*/) override
181{
182// do nothing
183}
184
185void CalculateGridLength(int /*iCtGridPerAxis*/) override
186{
187// do nothing
188}
189
190unsigned long HasElements() const override
191{
192return _pclMesh->CountFacets();
193}
194
195void Pos(const Base::Vector3f& rclPoint,
196unsigned long& rulX,
197unsigned long& rulY,
198unsigned long& rulZ) const
199{
200rulX = (unsigned long)((rclPoint.x - _fMinX) / _fGridLenX);
201rulY = (unsigned long)((rclPoint.y - _fMinY) / _fGridLenY);
202rulZ = (unsigned long)((rclPoint.z - _fMinZ) / _fGridLenZ);
203
204assert((rulX < _ulCtGridsX) && (rulY < _ulCtGridsY) && (rulZ < _ulCtGridsZ));
205}
206
207void AddFacet(const MeshCore::MeshGeomFacet& rclFacet, unsigned long ulFacetIndex)
208{
209unsigned long ulX1;
210unsigned long ulY1;
211unsigned long ulZ1;
212unsigned long ulX2;
213unsigned long ulY2;
214unsigned long ulZ2;
215
216Base::BoundBox3f clBB;
217clBB.Add(rclFacet._aclPoints[0]);
218clBB.Add(rclFacet._aclPoints[1]);
219clBB.Add(rclFacet._aclPoints[2]);
220
221Pos(Base::Vector3f(clBB.MinX, clBB.MinY, clBB.MinZ), ulX1, ulY1, ulZ1);
222Pos(Base::Vector3f(clBB.MaxX, clBB.MaxY, clBB.MaxZ), ulX2, ulY2, ulZ2);
223
224
225if ((ulX1 < ulX2) || (ulY1 < ulY2) || (ulZ1 < ulZ2)) {
226for (unsigned long ulX = ulX1; ulX <= ulX2; ulX++) {
227for (unsigned long ulY = ulY1; ulY <= ulY2; ulY++) {
228for (unsigned long ulZ = ulZ1; ulZ <= ulZ2; ulZ++) {
229if (rclFacet.IntersectBoundingBox(GetBoundBox(ulX, ulY, ulZ))) {
230_aulGrid[ulX][ulY][ulZ].insert(ulFacetIndex);
231}
232}
233}
234}
235}
236else {
237_aulGrid[ulX1][ulY1][ulZ1].insert(ulFacetIndex);
238}
239}
240
241void InitGrid() override
242{
243unsigned long i, j;
244
245Base::BoundBox3f clBBMesh = _pclMesh->GetBoundBox().Transformed(_transform);
246
247float fLengthX = clBBMesh.LengthX();
248float fLengthY = clBBMesh.LengthY();
249float fLengthZ = clBBMesh.LengthZ();
250
251_fGridLenX = (1.0f + fLengthX) / float(_ulCtGridsX);
252_fMinX = clBBMesh.MinX - 0.5f;
253
254_fGridLenY = (1.0f + fLengthY) / float(_ulCtGridsY);
255_fMinY = clBBMesh.MinY - 0.5f;
256
257_fGridLenZ = (1.0f + fLengthZ) / float(_ulCtGridsZ);
258_fMinZ = clBBMesh.MinZ - 0.5f;
259
260_aulGrid.clear();
261_aulGrid.resize(_ulCtGridsX);
262for (i = 0; i < _ulCtGridsX; i++) {
263_aulGrid[i].resize(_ulCtGridsY);
264for (j = 0; j < _ulCtGridsY; j++) {
265_aulGrid[i][j].resize(_ulCtGridsZ);
266}
267}
268}
269
270void RebuildGrid() override
271{
272_ulCtElements = _pclMesh->CountFacets();
273InitGrid();
274
275unsigned long i = 0;
276MeshCore::MeshFacetIterator clFIter(*_pclMesh);
277clFIter.Transform(_transform);
278for (clFIter.Init(); clFIter.More(); clFIter.Next()) {
279AddFacet(*clFIter, i++);
280}
281}
282
283private:
284Base::Matrix4D _transform;
285};
286} // namespace Inspection
287
288InspectNominalMesh::InspectNominalMesh(const Mesh::MeshObject& rMesh, float offset)
289: _mesh(rMesh.getKernel())
290{
291Base::Matrix4D tmp;
292_clTrf = rMesh.getTransform();
293_bApply = _clTrf != tmp;
294
295// Max. limit of grid elements
296float fMaxGridElements = 8000000.0f;
297Base::BoundBox3f box = _mesh.GetBoundBox().Transformed(rMesh.getTransform());
298
299// estimate the minimum allowed grid length
300float fMinGridLen =
301(float)pow((box.LengthX() * box.LengthY() * box.LengthZ() / fMaxGridElements), 0.3333f);
302float fGridLen = 5.0f * MeshCore::MeshAlgorithm(_mesh).GetAverageEdgeLength();
303
304// We want to avoid to get too small grid elements otherwise building up the grid structure
305// would take too much time and memory. Having quite a dense grid speeds up more the following
306// algorithms extremely. Due to the issue above it's always a compromise between speed and
307// memory usage.
308fGridLen = std::max<float>(fMinGridLen, fGridLen);
309
310// build up grid structure to speed up algorithms
311_pGrid = new MeshInspectGrid(_mesh, fGridLen, rMesh.getTransform());
312_box = box;
313_box.Enlarge(offset);
314}
315
316InspectNominalMesh::~InspectNominalMesh()
317{
318delete this->_pGrid;
319}
320
321float InspectNominalMesh::getDistance(const Base::Vector3f& point) const
322{
323if (!_box.IsInBox(point)) {
324return FLT_MAX; // must be inside bbox
325}
326
327std::vector<unsigned long> indices;
328//_pGrid->GetElements(point, indices);
329if (indices.empty()) {
330std::set<unsigned long> inds;
331_pGrid->MeshGrid::SearchNearestFromPoint(point, inds);
332indices.insert(indices.begin(), inds.begin(), inds.end());
333}
334
335float fMinDist = FLT_MAX;
336bool positive = true;
337for (unsigned long it : indices) {
338MeshCore::MeshGeomFacet geomFace = _mesh.GetFacet(it);
339if (_bApply) {
340geomFace.Transform(_clTrf);
341}
342
343float fDist = geomFace.DistanceToPoint(point);
344if (fabs(fDist) < fabs(fMinDist)) {
345fMinDist = fDist;
346positive = point.DistanceToPlane(geomFace._aclPoints[0], geomFace.GetNormal()) > 0;
347}
348}
349
350if (!positive) {
351fMinDist = -fMinDist;
352}
353return fMinDist;
354}
355
356// ----------------------------------------------------------------
357
358InspectNominalFastMesh::InspectNominalFastMesh(const Mesh::MeshObject& rMesh, float offset)
359: _mesh(rMesh.getKernel())
360{
361const MeshCore::MeshKernel& kernel = rMesh.getKernel();
362
363Base::Matrix4D tmp;
364_clTrf = rMesh.getTransform();
365_bApply = _clTrf != tmp;
366
367// Max. limit of grid elements
368float fMaxGridElements = 8000000.0f;
369Base::BoundBox3f box = kernel.GetBoundBox().Transformed(rMesh.getTransform());
370
371// estimate the minimum allowed grid length
372float fMinGridLen =
373(float)pow((box.LengthX() * box.LengthY() * box.LengthZ() / fMaxGridElements), 0.3333f);
374float fGridLen = 5.0f * MeshCore::MeshAlgorithm(kernel).GetAverageEdgeLength();
375
376// We want to avoid to get too small grid elements otherwise building up the grid structure
377// would take too much time and memory. Having quite a dense grid speeds up more the following
378// algorithms extremely. Due to the issue above it's always a compromise between speed and
379// memory usage.
380fGridLen = std::max<float>(fMinGridLen, fGridLen);
381
382// build up grid structure to speed up algorithms
383_pGrid = new MeshInspectGrid(kernel, fGridLen, rMesh.getTransform());
384_box = box;
385_box.Enlarge(offset);
386max_level = (unsigned long)(offset / fGridLen);
387}
388
389InspectNominalFastMesh::~InspectNominalFastMesh()
390{
391delete this->_pGrid;
392}
393
394/**
395* This algorithm is not that exact as that from InspectNominalMesh but is by
396* factors faster and sufficient for many cases.
397*/
398float InspectNominalFastMesh::getDistance(const Base::Vector3f& point) const
399{
400if (!_box.IsInBox(point)) {
401return FLT_MAX; // must be inside bbox
402}
403
404std::set<unsigned long> indices;
405#if 0 // a point in a neighbour grid can be nearer
406std::vector<unsigned long> elements;
407_pGrid->GetElements(point, elements);
408indices.insert(elements.begin(), elements.end());
409#else
410unsigned long ulX, ulY, ulZ;
411_pGrid->Position(point, ulX, ulY, ulZ);
412unsigned long ulLevel = 0;
413while (indices.empty() && ulLevel <= max_level) {
414_pGrid->GetHull(ulX, ulY, ulZ, ulLevel++, indices);
415}
416if (indices.empty() || ulLevel == 1) {
417_pGrid->GetHull(ulX, ulY, ulZ, ulLevel, indices);
418}
419#endif
420
421float fMinDist = FLT_MAX;
422bool positive = true;
423for (unsigned long it : indices) {
424MeshCore::MeshGeomFacet geomFace = _mesh.GetFacet(it);
425if (_bApply) {
426geomFace.Transform(_clTrf);
427}
428
429float fDist = geomFace.DistanceToPoint(point);
430if (fabs(fDist) < fabs(fMinDist)) {
431fMinDist = fDist;
432positive = point.DistanceToPlane(geomFace._aclPoints[0], geomFace.GetNormal()) > 0;
433}
434}
435
436if (!positive) {
437fMinDist = -fMinDist;
438}
439return fMinDist;
440}
441
442// ----------------------------------------------------------------
443
444InspectNominalPoints::InspectNominalPoints(const Points::PointKernel& Kernel, float /*offset*/)
445: _rKernel(Kernel)
446{
447int uGridPerAxis = 50; // totally 125.000 grid elements
448this->_pGrid = new Points::PointsGrid(Kernel, uGridPerAxis);
449}
450
451InspectNominalPoints::~InspectNominalPoints()
452{
453delete this->_pGrid;
454}
455
456float InspectNominalPoints::getDistance(const Base::Vector3f& point) const
457{
458// TODO: Make faster
459std::set<unsigned long> indices;
460unsigned long x, y, z;
461Base::Vector3d pointd(point.x, point.y, point.z);
462_pGrid->Position(pointd, x, y, z);
463_pGrid->GetElements(x, y, z, indices);
464
465double fMinDist = DBL_MAX;
466for (unsigned long it : indices) {
467Base::Vector3d pt = _rKernel.getPoint(it);
468double fDist = Base::Distance(pointd, pt);
469if (fDist < fMinDist) {
470fMinDist = fDist;
471}
472}
473
474return (float)fMinDist;
475}
476
477// ----------------------------------------------------------------
478
479InspectNominalShape::InspectNominalShape(const TopoDS_Shape& shape, float /*radius*/)
480: _rShape(shape)
481{
482distss = new BRepExtrema_DistShapeShape();
483distss->LoadS1(_rShape);
484
485// When having a solid then use its shell because otherwise the distance
486// for inner points will always be zero
487if (!_rShape.IsNull() && _rShape.ShapeType() == TopAbs_SOLID) {
488TopExp_Explorer xp;
489xp.Init(_rShape, TopAbs_SHELL);
490if (xp.More()) {
491distss->LoadS1(xp.Current());
492isSolid = true;
493}
494}
495// distss->SetDeflection(radius);
496}
497
498InspectNominalShape::~InspectNominalShape()
499{
500delete distss;
501}
502
503float InspectNominalShape::getDistance(const Base::Vector3f& point) const
504{
505gp_Pnt pnt3d(point.x, point.y, point.z);
506BRepBuilderAPI_MakeVertex mkVert(pnt3d);
507distss->LoadS2(mkVert.Vertex());
508
509float fMinDist = FLT_MAX;
510if (distss->Perform() && distss->NbSolution() > 0) {
511fMinDist = (float)distss->Value();
512// the shape is a solid, check if the vertex is inside
513if (isSolid) {
514if (isInsideSolid(pnt3d)) {
515fMinDist = -fMinDist;
516}
517}
518else if (fMinDist > 0) {
519// check if the distance was computed from a face
520if (isBelowFace(pnt3d)) {
521fMinDist = -fMinDist;
522}
523}
524}
525return fMinDist;
526}
527
528bool InspectNominalShape::isInsideSolid(const gp_Pnt& pnt3d) const
529{
530const Standard_Real tol = 0.001;
531BRepClass3d_SolidClassifier classifier(_rShape);
532classifier.Perform(pnt3d, tol);
533return (classifier.State() == TopAbs_IN);
534}
535
536bool InspectNominalShape::isBelowFace(const gp_Pnt& pnt3d) const
537{
538// check if the distance was computed from a face
539for (Standard_Integer index = 1; index <= distss->NbSolution(); index++) {
540if (distss->SupportTypeShape1(index) == BRepExtrema_IsInFace) {
541TopoDS_Shape face = distss->SupportOnShape1(index);
542Standard_Real u, v;
543distss->ParOnFaceS1(index, u, v);
544// gp_Pnt pnt = distss->PointOnShape1(index);
545BRepGProp_Face props(TopoDS::Face(face));
546gp_Vec normal;
547gp_Pnt center;
548props.Normal(u, v, center, normal);
549gp_Vec dir(center, pnt3d);
550Standard_Real scalar = normal.Dot(dir);
551if (scalar < 0) {
552return true;
553}
554break;
555}
556}
557
558return false;
559}
560
561// ----------------------------------------------------------------
562
563TYPESYSTEM_SOURCE(Inspection::PropertyDistanceList, App::PropertyLists)
564
565PropertyDistanceList::PropertyDistanceList() = default;
566
567PropertyDistanceList::~PropertyDistanceList() = default;
568
569void PropertyDistanceList::setSize(int newSize)
570{
571_lValueList.resize(newSize);
572}
573
574int PropertyDistanceList::getSize() const
575{
576return static_cast<int>(_lValueList.size());
577}
578
579void PropertyDistanceList::setValue(float lValue)
580{
581aboutToSetValue();
582_lValueList.resize(1);
583_lValueList[0] = lValue;
584hasSetValue();
585}
586
587void PropertyDistanceList::setValues(const std::vector<float>& values)
588{
589aboutToSetValue();
590_lValueList = values;
591hasSetValue();
592}
593
594PyObject* PropertyDistanceList::getPyObject()
595{
596PyObject* list = PyList_New(getSize());
597for (int i = 0; i < getSize(); i++) {
598PyList_SetItem(list, i, PyFloat_FromDouble(_lValueList[i]));
599}
600return list;
601}
602
603void PropertyDistanceList::setPyObject(PyObject* value)
604{
605if (PyList_Check(value)) {
606Py_ssize_t nSize = PyList_Size(value);
607std::vector<float> values;
608values.resize(nSize);
609
610for (Py_ssize_t i = 0; i < nSize; ++i) {
611PyObject* item = PyList_GetItem(value, i);
612if (!PyFloat_Check(item)) {
613std::string error = std::string("type in list must be float, not ");
614error += item->ob_type->tp_name;
615throw Py::TypeError(error);
616}
617
618values[i] = (float)PyFloat_AsDouble(item);
619}
620
621setValues(values);
622}
623else if (PyFloat_Check(value)) {
624setValue((float)PyFloat_AsDouble(value));
625}
626else {
627std::string error = std::string("type must be float or list of float, not ");
628error += value->ob_type->tp_name;
629throw Py::TypeError(error);
630}
631}
632
633void PropertyDistanceList::Save(Base::Writer& writer) const
634{
635if (writer.isForceXML()) {
636writer.Stream() << writer.ind() << "<FloatList count=\"" << getSize() << "\">" << std::endl;
637writer.incInd();
638for (int i = 0; i < getSize(); i++) {
639writer.Stream() << writer.ind() << "<F v=\"" << _lValueList[i] << "\"/>" << std::endl;
640}
641writer.decInd();
642writer.Stream() << writer.ind() << "</FloatList>" << std::endl;
643}
644else {
645writer.Stream() << writer.ind() << "<FloatList file=\"" << writer.addFile(getName(), this)
646<< "\"/>" << std::endl;
647}
648}
649
650void PropertyDistanceList::Restore(Base::XMLReader& reader)
651{
652reader.readElement("FloatList");
653std::string file(reader.getAttribute("file"));
654
655if (!file.empty()) {
656// initiate a file read
657reader.addFile(file.c_str(), this);
658}
659}
660
661void PropertyDistanceList::SaveDocFile(Base::Writer& writer) const
662{
663Base::OutputStream str(writer.Stream());
664uint32_t uCt = (uint32_t)getSize();
665str << uCt;
666for (float it : _lValueList) {
667str << it;
668}
669}
670
671void PropertyDistanceList::RestoreDocFile(Base::Reader& reader)
672{
673Base::InputStream str(reader);
674uint32_t uCt = 0;
675str >> uCt;
676std::vector<float> values(uCt);
677for (float& it : values) {
678str >> it;
679}
680setValues(values);
681}
682
683App::Property* PropertyDistanceList::Copy() const
684{
685PropertyDistanceList* p = new PropertyDistanceList();
686p->_lValueList = _lValueList;
687return p;
688}
689
690void PropertyDistanceList::Paste(const App::Property& from)
691{
692aboutToSetValue();
693_lValueList = dynamic_cast<const PropertyDistanceList&>(from)._lValueList;
694hasSetValue();
695}
696
697unsigned int PropertyDistanceList::getMemSize() const
698{
699return static_cast<unsigned int>(_lValueList.size() * sizeof(float));
700}
701
702// ----------------------------------------------------------------
703
704namespace Inspection
705{
706// helper class to use Qt's concurrent framework
707struct DistanceInspection
708{
709
710DistanceInspection(float radius,
711InspectActualGeometry* a,
712std::vector<InspectNominalGeometry*> n)
713: radius(radius)
714, actual(a)
715, nominal(n)
716{}
717float mapped(unsigned long index) const
718{
719Base::Vector3f pnt = actual->getPoint(index);
720
721float fMinDist = FLT_MAX;
722for (auto it : nominal) {
723float fDist = it->getDistance(pnt);
724if (fabs(fDist) < fabs(fMinDist)) {
725fMinDist = fDist;
726}
727}
728
729if (fMinDist > this->radius) {
730fMinDist = FLT_MAX;
731}
732else if (-fMinDist > this->radius) {
733fMinDist = -FLT_MAX;
734}
735
736return fMinDist;
737}
738
739float radius;
740InspectActualGeometry* actual;
741std::vector<InspectNominalGeometry*> nominal;
742};
743
744// Helper internal class for QtConcurrent map operation. Holds sums-of-squares and counts for RMS
745// calculation
746class DistanceInspectionRMS
747{
748public:
749DistanceInspectionRMS() = default;
750DistanceInspectionRMS& operator+=(const DistanceInspectionRMS& rhs)
751{
752this->m_numv += rhs.m_numv;
753this->m_sumsq += rhs.m_sumsq;
754return *this;
755}
756double getRMS()
757{
758if (this->m_numv == 0) {
759return 0.0;
760}
761return sqrt(this->m_sumsq / (double)this->m_numv);
762}
763int m_numv {0};
764double m_sumsq {0.0};
765};
766} // namespace Inspection
767
768PROPERTY_SOURCE(Inspection::Feature, App::DocumentObject)
769
770Feature::Feature()
771{
772ADD_PROPERTY(SearchRadius, (0.05));
773ADD_PROPERTY(Thickness, (0.0));
774ADD_PROPERTY(Actual, (nullptr));
775ADD_PROPERTY(Nominals, (nullptr));
776ADD_PROPERTY(Distances, (0.0));
777}
778
779Feature::~Feature() = default;
780
781short Feature::mustExecute() const
782{
783if (SearchRadius.isTouched()) {
784return 1;
785}
786if (Thickness.isTouched()) {
787return 1;
788}
789if (Actual.isTouched()) {
790return 1;
791}
792if (Nominals.isTouched()) {
793return 1;
794}
795return 0;
796}
797
798App::DocumentObjectExecReturn* Feature::execute()
799{
800bool useMultithreading = true;
801
802App::DocumentObject* pcActual = Actual.getValue();
803if (!pcActual) {
804throw Base::ValueError("No actual geometry to inspect specified");
805}
806
807InspectActualGeometry* actual = nullptr;
808if (pcActual->isDerivedFrom<Mesh::Feature>()) {
809Mesh::Feature* mesh = static_cast<Mesh::Feature*>(pcActual);
810actual = new InspectActualMesh(mesh->Mesh.getValue());
811}
812else if (pcActual->isDerivedFrom<Points::Feature>()) {
813Points::Feature* pts = static_cast<Points::Feature*>(pcActual);
814actual = new InspectActualPoints(pts->Points.getValue());
815}
816else if (pcActual->isDerivedFrom<Part::Feature>()) {
817useMultithreading = false;
818Part::Feature* part = static_cast<Part::Feature*>(pcActual);
819actual = new InspectActualShape(part->Shape.getShape());
820}
821else {
822throw Base::TypeError("Unknown geometric type");
823}
824
825// clang-format off
826// get a list of nominals
827std::vector<InspectNominalGeometry*> inspectNominal;
828const std::vector<App::DocumentObject*>& nominals = Nominals.getValues();
829for (auto it : nominals) {
830InspectNominalGeometry* nominal = nullptr;
831if (it->isDerivedFrom<Mesh::Feature>()) {
832Mesh::Feature* mesh = static_cast<Mesh::Feature*>(it);
833nominal = new InspectNominalMesh(mesh->Mesh.getValue(), this->SearchRadius.getValue());
834}
835else if (it->isDerivedFrom<Points::Feature>()) {
836Points::Feature* pts = static_cast<Points::Feature*>(it);
837nominal = new InspectNominalPoints(pts->Points.getValue(), this->SearchRadius.getValue());
838}
839else if (it->isDerivedFrom<Part::Feature>()) {
840useMultithreading = false;
841Part::Feature* part = static_cast<Part::Feature*>(it);
842nominal = new InspectNominalShape(part->Shape.getValue(), this->SearchRadius.getValue());
843}
844
845if (nominal) {
846inspectNominal.push_back(nominal);
847}
848}
849// clang-format on
850
851#if 0
852#if 1 // test with some huge data sets
853std::vector<unsigned long> index(actual->countPoints());
854std::generate(index.begin(), index.end(), Base::iotaGen<unsigned long>(0));
855DistanceInspection check(this->SearchRadius.getValue(), actual, inspectNominal);
856QFuture<float> future = QtConcurrent::mapped
857(index, std::bind(&DistanceInspection::mapped, &check, sp::_1));
858//future.waitForFinished(); // blocks the GUI
859Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints());
860QFutureWatcher<float> watcher;
861QObject::connect(&watcher, &QFutureWatcher<float>::progressValueChanged,
862&progress, &Base::FutureWatcherProgress::progressValueChanged);
863
864// keep it responsive during computation
865QEventLoop loop;
866QObject::connect(&watcher, &QFutureWatcher::finished, &loop, &QEventLoop::quit);
867watcher.setFuture(future);
868loop.exec();
869
870std::vector<float> vals;
871vals.insert(vals.end(), future.begin(), future.end());
872#else
873DistanceInspection insp(this->SearchRadius.getValue(), actual, inspectNominal);
874unsigned long count = actual->countPoints();
875std::stringstream str;
876str << "Inspecting " << this->Label.getValue() << "...";
877Base::SequencerLauncher seq(str.str().c_str(), count);
878
879std::vector<float> vals(count);
880for (unsigned long index = 0; index < count; index++) {
881float fMinDist = insp.mapped(index);
882vals[index] = fMinDist;
883seq.next();
884}
885#endif
886
887Distances.setValues(vals);
888
889float fRMS = 0;
890int countRMS = 0;
891for (std::vector<float>::iterator it = vals.begin(); it != vals.end(); ++it) {
892if (fabs(*it) < FLT_MAX) {
893fRMS += (*it) * (*it);
894countRMS++;
895}
896}
897
898if (countRMS > 0) {
899fRMS = fRMS / countRMS;
900fRMS = sqrt(fRMS);
901}
902
903Base::Console().Message("RMS value for '%s' with search radius [%.4f,%.4f] is: %.4f\n",
904this->Label.getValue(), -this->SearchRadius.getValue(), this->SearchRadius.getValue(), fRMS);
905#else
906unsigned long count = actual->countPoints();
907std::vector<float> vals(count);
908std::function<DistanceInspectionRMS(int)> fMap = [&](unsigned int index) {
909DistanceInspectionRMS res;
910Base::Vector3f pnt = actual->getPoint(index);
911
912float fMinDist = FLT_MAX;
913for (auto it : inspectNominal) {
914float fDist = it->getDistance(pnt);
915if (fabs(fDist) < fabs(fMinDist)) {
916fMinDist = fDist;
917}
918}
919
920if (fMinDist > this->SearchRadius.getValue()) {
921fMinDist = FLT_MAX;
922}
923else if (-fMinDist > this->SearchRadius.getValue()) {
924fMinDist = -FLT_MAX;
925}
926else {
927res.m_sumsq += fMinDist * fMinDist;
928res.m_numv++;
929}
930
931vals[index] = fMinDist;
932return res;
933};
934
935DistanceInspectionRMS res;
936
937if (useMultithreading) {
938// Build vector of increasing indices
939std::vector<unsigned long> index(count);
940std::iota(index.begin(), index.end(), 0);
941// Perform map-reduce operation : compute distances and update sum of squares for RMS
942// computation
943QFuture<DistanceInspectionRMS> future =
944QtConcurrent::mappedReduced(index, fMap, &DistanceInspectionRMS::operator+=);
945// Setup progress bar
946Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints());
947QFutureWatcher<DistanceInspectionRMS> watcher;
948QObject::connect(&watcher,
949&QFutureWatcher<DistanceInspectionRMS>::progressValueChanged,
950&progress,
951&Base::FutureWatcherProgress::progressValueChanged);
952// Keep UI responsive during computation
953QEventLoop loop;
954QObject::connect(&watcher,
955&QFutureWatcher<DistanceInspectionRMS>::finished,
956&loop,
957&QEventLoop::quit);
958watcher.setFuture(future);
959loop.exec();
960res = future.result();
961}
962else {
963// Single-threaded operation
964std::stringstream str;
965str << "Inspecting " << this->Label.getValue() << "...";
966Base::SequencerLauncher seq(str.str().c_str(), count);
967
968for (unsigned int i = 0; i < count; i++) {
969res += fMap(i);
970}
971}
972
973Base::Console().Message("RMS value for '%s' with search radius [%.4f,%.4f] is: %.4f\n",
974this->Label.getValue(),
975-this->SearchRadius.getValue(),
976this->SearchRadius.getValue(),
977res.getRMS());
978Distances.setValues(vals);
979#endif
980
981delete actual;
982for (auto it : inspectNominal) {
983delete it;
984}
985
986return nullptr;
987}
988
989// ----------------------------------------------------------------
990
991PROPERTY_SOURCE(Inspection::Group, App::DocumentObjectGroup)
992
993
994Group::Group() = default;
995
996Group::~Group() = default;
997