// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2011 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "InspectionFeature.h" using namespace Inspection; namespace sp = std::placeholders; InspectActualMesh::InspectActualMesh(const Mesh::MeshObject& rMesh) : _mesh(rMesh.getKernel()) { Base::Matrix4D tmp; _clTrf = rMesh.getTransform(); _bApply = _clTrf != tmp; } InspectActualMesh::~InspectActualMesh() = default; unsigned long InspectActualMesh::countPoints() const { return _mesh.CountPoints(); } Base::Vector3f InspectActualMesh::getPoint(unsigned long index) const { Base::Vector3f point = _mesh.GetPoint(index); if (_bApply) { _clTrf.multVec(point, point); } return point; } // ---------------------------------------------------------------- InspectActualPoints::InspectActualPoints(const Points::PointKernel& rPoints) : _rKernel(rPoints) {} unsigned long InspectActualPoints::countPoints() const { return _rKernel.size(); } Base::Vector3f InspectActualPoints::getPoint(unsigned long index) const { Base::Vector3d pnt = _rKernel.getPoint(index); return Base::Vector3f(float(pnt.x), float(pnt.y), float(pnt.z)); } // ---------------------------------------------------------------- InspectActualShape::InspectActualShape(const Part::TopoShape& shape) : _rShape(shape) { Standard_Real deflection = _rShape.getAccuracy(); fetchPoints(deflection); } void InspectActualShape::fetchPoints(double deflection) { // get points from faces or sub-sampled edges TopTools_IndexedMapOfShape mapOfShapes; TopExp::MapShapes(_rShape.getShape(), TopAbs_FACE, mapOfShapes); if (!mapOfShapes.IsEmpty()) { std::vector faces; _rShape.getFaces(points, faces, deflection); } else { TopExp::MapShapes(_rShape.getShape(), TopAbs_EDGE, mapOfShapes); if (!mapOfShapes.IsEmpty()) { std::vector lines; _rShape.getLines(points, lines, deflection); } else { std::vector normals; _rShape.getPoints(points, normals, deflection); } } } unsigned long InspectActualShape::countPoints() const { return points.size(); } Base::Vector3f InspectActualShape::getPoint(unsigned long index) const { return Base::toVector(points[index]); } // ---------------------------------------------------------------- namespace Inspection { class MeshInspectGrid: public MeshCore::MeshGrid { public: MeshInspectGrid(const MeshCore::MeshKernel& mesh, float fGridLen, const Base::Matrix4D& mat) : MeshCore::MeshGrid(mesh) , _transform(mat) { Base::BoundBox3f clBBMesh = _pclMesh->GetBoundBox().Transformed(mat); Rebuild( std::max((unsigned long)(clBBMesh.LengthX() / fGridLen), 1), std::max((unsigned long)(clBBMesh.LengthY() / fGridLen), 1), std::max((unsigned long)(clBBMesh.LengthZ() / fGridLen), 1) ); } void Validate(const MeshCore::MeshKernel& kernel) override { // do nothing boost::ignore_unused(kernel); } void Validate() { // do nothing } bool Verify() const override { // do nothing return true; } protected: void CalculateGridLength(int /*iCtGridPerAxis*/) override { // do nothing } unsigned long HasElements() const override { return _pclMesh->CountFacets(); } void Pos( const Base::Vector3f& rclPoint, unsigned long& rulX, unsigned long& rulY, unsigned long& rulZ ) const { rulX = (unsigned long)((rclPoint.x - _fMinX) / _fGridLenX); rulY = (unsigned long)((rclPoint.y - _fMinY) / _fGridLenY); rulZ = (unsigned long)((rclPoint.z - _fMinZ) / _fGridLenZ); assert((rulX < _ulCtGridsX) && (rulY < _ulCtGridsY) && (rulZ < _ulCtGridsZ)); } void AddFacet(const MeshCore::MeshGeomFacet& rclFacet, unsigned long ulFacetIndex) { unsigned long ulX1; unsigned long ulY1; unsigned long ulZ1; unsigned long ulX2; unsigned long ulY2; unsigned long ulZ2; Base::BoundBox3f clBB; clBB.Add(rclFacet._aclPoints[0]); clBB.Add(rclFacet._aclPoints[1]); clBB.Add(rclFacet._aclPoints[2]); Pos(Base::Vector3f(clBB.MinX, clBB.MinY, clBB.MinZ), ulX1, ulY1, ulZ1); Pos(Base::Vector3f(clBB.MaxX, clBB.MaxY, clBB.MaxZ), ulX2, ulY2, ulZ2); if ((ulX1 < ulX2) || (ulY1 < ulY2) || (ulZ1 < ulZ2)) { for (unsigned long ulX = ulX1; ulX <= ulX2; ulX++) { for (unsigned long ulY = ulY1; ulY <= ulY2; ulY++) { for (unsigned long ulZ = ulZ1; ulZ <= ulZ2; ulZ++) { if (rclFacet.IntersectBoundingBox(GetBoundBox(ulX, ulY, ulZ))) { _aulGrid[ulX][ulY][ulZ].insert(ulFacetIndex); } } } } } else { _aulGrid[ulX1][ulY1][ulZ1].insert(ulFacetIndex); } } void InitGrid() override { unsigned long i, j; Base::BoundBox3f clBBMesh = _pclMesh->GetBoundBox().Transformed(_transform); float fLengthX = clBBMesh.LengthX(); float fLengthY = clBBMesh.LengthY(); float fLengthZ = clBBMesh.LengthZ(); _fGridLenX = (1.0f + fLengthX) / float(_ulCtGridsX); _fMinX = clBBMesh.MinX - 0.5f; _fGridLenY = (1.0f + fLengthY) / float(_ulCtGridsY); _fMinY = clBBMesh.MinY - 0.5f; _fGridLenZ = (1.0f + fLengthZ) / float(_ulCtGridsZ); _fMinZ = clBBMesh.MinZ - 0.5f; _aulGrid.clear(); _aulGrid.resize(_ulCtGridsX); for (i = 0; i < _ulCtGridsX; i++) { _aulGrid[i].resize(_ulCtGridsY); for (j = 0; j < _ulCtGridsY; j++) { _aulGrid[i][j].resize(_ulCtGridsZ); } } } void RebuildGrid() override { _ulCtElements = _pclMesh->CountFacets(); InitGrid(); unsigned long i = 0; MeshCore::MeshFacetIterator clFIter(*_pclMesh); clFIter.Transform(_transform); for (clFIter.Init(); clFIter.More(); clFIter.Next()) { AddFacet(*clFIter, i++); } } private: Base::Matrix4D _transform; }; } // namespace Inspection InspectNominalMesh::InspectNominalMesh(const Mesh::MeshObject& rMesh, float offset) : _mesh(rMesh.getKernel()) { Base::Matrix4D tmp; _clTrf = rMesh.getTransform(); _bApply = _clTrf != tmp; // Max. limit of grid elements float fMaxGridElements = 8000000.0f; Base::BoundBox3f box = _mesh.GetBoundBox().Transformed(rMesh.getTransform()); // estimate the minimum allowed grid length float fMinGridLen = (float)pow((box.LengthX() * box.LengthY() * box.LengthZ() / fMaxGridElements), 0.3333f); float fGridLen = 5.0f * MeshCore::MeshAlgorithm(_mesh).GetAverageEdgeLength(); // We want to avoid to get too small grid elements otherwise building up the grid structure // would take too much time and memory. Having quite a dense grid speeds up more the following // algorithms extremely. Due to the issue above it's always a compromise between speed and // memory usage. fGridLen = std::max(fMinGridLen, fGridLen); // build up grid structure to speed up algorithms _pGrid = new MeshInspectGrid(_mesh, fGridLen, rMesh.getTransform()); _box = box; _box.Enlarge(offset); } InspectNominalMesh::~InspectNominalMesh() { delete this->_pGrid; } float InspectNominalMesh::getDistance(const Base::Vector3f& point) const { if (!_box.IsInBox(point)) { return std::numeric_limits::max(); // must be inside bbox } std::vector indices; //_pGrid->GetElements(point, indices); if (indices.empty()) { std::set inds; _pGrid->MeshGrid::SearchNearestFromPoint(point, inds); indices.insert(indices.begin(), inds.begin(), inds.end()); } float fMinDist = std::numeric_limits::max(); bool positive = true; for (unsigned long it : indices) { MeshCore::MeshGeomFacet geomFace = _mesh.GetFacet(it); if (_bApply) { geomFace.Transform(_clTrf); } float fDist = geomFace.DistanceToPoint(point); if (fabs(fDist) < fabs(fMinDist)) { fMinDist = fDist; positive = point.DistanceToPlane(geomFace._aclPoints[0], geomFace.GetNormal()) > 0; } } if (!positive) { fMinDist = -fMinDist; } return fMinDist; } // ---------------------------------------------------------------- InspectNominalFastMesh::InspectNominalFastMesh(const Mesh::MeshObject& rMesh, float offset) : _mesh(rMesh.getKernel()) { const MeshCore::MeshKernel& kernel = rMesh.getKernel(); Base::Matrix4D tmp; _clTrf = rMesh.getTransform(); _bApply = _clTrf != tmp; // Max. limit of grid elements float fMaxGridElements = 8000000.0f; Base::BoundBox3f box = kernel.GetBoundBox().Transformed(rMesh.getTransform()); // estimate the minimum allowed grid length float fMinGridLen = (float)pow((box.LengthX() * box.LengthY() * box.LengthZ() / fMaxGridElements), 0.3333f); float fGridLen = 5.0f * MeshCore::MeshAlgorithm(kernel).GetAverageEdgeLength(); // We want to avoid to get too small grid elements otherwise building up the grid structure // would take too much time and memory. Having quite a dense grid speeds up more the following // algorithms extremely. Due to the issue above it's always a compromise between speed and // memory usage. fGridLen = std::max(fMinGridLen, fGridLen); // build up grid structure to speed up algorithms _pGrid = new MeshInspectGrid(kernel, fGridLen, rMesh.getTransform()); _box = box; _box.Enlarge(offset); max_level = (unsigned long)(offset / fGridLen); } InspectNominalFastMesh::~InspectNominalFastMesh() { delete this->_pGrid; } /** * This algorithm is not that exact as that from InspectNominalMesh but is by * factors faster and sufficient for many cases. */ float InspectNominalFastMesh::getDistance(const Base::Vector3f& point) const { if (!_box.IsInBox(point)) { return std::numeric_limits::max(); // must be inside bbox } std::set indices; #if 0 // a point in a neighbour grid can be nearer std::vector elements; _pGrid->GetElements(point, elements); indices.insert(elements.begin(), elements.end()); #else unsigned long ulX, ulY, ulZ; _pGrid->Position(point, ulX, ulY, ulZ); unsigned long ulLevel = 0; while (indices.empty() && ulLevel <= max_level) { _pGrid->GetHull(ulX, ulY, ulZ, ulLevel++, indices); } if (indices.empty() || ulLevel == 1) { _pGrid->GetHull(ulX, ulY, ulZ, ulLevel, indices); } #endif float fMinDist = std::numeric_limits::max(); bool positive = true; for (unsigned long it : indices) { MeshCore::MeshGeomFacet geomFace = _mesh.GetFacet(it); if (_bApply) { geomFace.Transform(_clTrf); } float fDist = geomFace.DistanceToPoint(point); if (fabs(fDist) < fabs(fMinDist)) { fMinDist = fDist; positive = point.DistanceToPlane(geomFace._aclPoints[0], geomFace.GetNormal()) > 0; } } if (!positive) { fMinDist = -fMinDist; } return fMinDist; } // ---------------------------------------------------------------- InspectNominalPoints::InspectNominalPoints(const Points::PointKernel& Kernel, float /*offset*/) : _rKernel(Kernel) { int uGridPerAxis = 50; // totally 125.000 grid elements this->_pGrid = new Points::PointsGrid(Kernel, uGridPerAxis); } InspectNominalPoints::~InspectNominalPoints() { delete this->_pGrid; } float InspectNominalPoints::getDistance(const Base::Vector3f& point) const { // TODO: Make faster std::set indices; unsigned long x, y, z; Base::Vector3d pointd(point.x, point.y, point.z); _pGrid->Position(pointd, x, y, z); _pGrid->GetElements(x, y, z, indices); double fMinDist = std::numeric_limits::max(); for (unsigned long it : indices) { Base::Vector3d pt = _rKernel.getPoint(it); double fDist = Base::Distance(pointd, pt); if (fDist < fMinDist) { fMinDist = fDist; } } return (float)fMinDist; } // ---------------------------------------------------------------- InspectNominalShape::InspectNominalShape(const TopoDS_Shape& shape, float /*radius*/) : _rShape(shape) { distss = new BRepExtrema_DistShapeShape(); distss->LoadS1(_rShape); // When having a solid then use its shell because otherwise the distance // for inner points will always be zero if (!_rShape.IsNull() && _rShape.ShapeType() == TopAbs_SOLID) { TopExp_Explorer xp; xp.Init(_rShape, TopAbs_SHELL); if (xp.More()) { distss->LoadS1(xp.Current()); isSolid = true; } } // distss->SetDeflection(radius); } InspectNominalShape::~InspectNominalShape() { delete distss; } float InspectNominalShape::getDistance(const Base::Vector3f& point) const { gp_Pnt pnt3d(point.x, point.y, point.z); BRepBuilderAPI_MakeVertex mkVert(pnt3d); distss->LoadS2(mkVert.Vertex()); float fMinDist = std::numeric_limits::max(); if (distss->Perform() && distss->NbSolution() > 0) { fMinDist = (float)distss->Value(); // the shape is a solid, check if the vertex is inside if (isSolid) { if (isInsideSolid(pnt3d)) { fMinDist = -fMinDist; } } else if (fMinDist > 0) { // check if the distance was computed from a face if (isBelowFace(pnt3d)) { fMinDist = -fMinDist; } } } return fMinDist; } bool InspectNominalShape::isInsideSolid(const gp_Pnt& pnt3d) const { const Standard_Real tol = 0.001; BRepClass3d_SolidClassifier classifier(_rShape); classifier.Perform(pnt3d, tol); return (classifier.State() == TopAbs_IN); } bool InspectNominalShape::isBelowFace(const gp_Pnt& pnt3d) const { // check if the distance was computed from a face for (Standard_Integer index = 1; index <= distss->NbSolution(); index++) { if (distss->SupportTypeShape1(index) == BRepExtrema_IsInFace) { TopoDS_Shape face = distss->SupportOnShape1(index); Standard_Real u, v; distss->ParOnFaceS1(index, u, v); // gp_Pnt pnt = distss->PointOnShape1(index); BRepGProp_Face props(TopoDS::Face(face)); gp_Vec normal; gp_Pnt center; props.Normal(u, v, center, normal); gp_Vec dir(center, pnt3d); Standard_Real scalar = normal.Dot(dir); if (scalar < 0) { return true; } break; } } return false; } // ---------------------------------------------------------------- TYPESYSTEM_SOURCE(Inspection::PropertyDistanceList, App::PropertyLists) PropertyDistanceList::PropertyDistanceList() = default; PropertyDistanceList::~PropertyDistanceList() = default; void PropertyDistanceList::setSize(int newSize) { _lValueList.resize(newSize); } int PropertyDistanceList::getSize() const { return static_cast(_lValueList.size()); } void PropertyDistanceList::setValue(float lValue) { aboutToSetValue(); _lValueList.resize(1); _lValueList[0] = lValue; hasSetValue(); } void PropertyDistanceList::setValues(const std::vector& values) { aboutToSetValue(); _lValueList = values; hasSetValue(); } PyObject* PropertyDistanceList::getPyObject() { PyObject* list = PyList_New(getSize()); for (int i = 0; i < getSize(); i++) { PyList_SetItem(list, i, PyFloat_FromDouble(_lValueList[i])); } return list; } void PropertyDistanceList::setPyObject(PyObject* value) { if (PyList_Check(value)) { Py_ssize_t nSize = PyList_Size(value); std::vector values; values.resize(nSize); for (Py_ssize_t i = 0; i < nSize; ++i) { PyObject* item = PyList_GetItem(value, i); if (!PyFloat_Check(item)) { std::string error = std::string("type in list must be float, not "); error += item->ob_type->tp_name; throw Py::TypeError(error); } values[i] = (float)PyFloat_AsDouble(item); } setValues(values); } else if (PyFloat_Check(value)) { setValue((float)PyFloat_AsDouble(value)); } else { std::string error = std::string("type must be float or list of float, not "); error += value->ob_type->tp_name; throw Py::TypeError(error); } } void PropertyDistanceList::Save(Base::Writer& writer) const { if (writer.isForceXML()) { writer.Stream() << writer.ind() << "" << std::endl; writer.incInd(); for (int i = 0; i < getSize(); i++) { writer.Stream() << writer.ind() << "" << std::endl; } writer.decInd(); writer.Stream() << writer.ind() << "" << std::endl; } else { writer.Stream() << writer.ind() << "" << std::endl; } } void PropertyDistanceList::Restore(Base::XMLReader& reader) { reader.readElement("FloatList"); std::string file(reader.getAttribute("file")); if (!file.empty()) { // initiate a file read reader.addFile(file.c_str(), this); } } void PropertyDistanceList::SaveDocFile(Base::Writer& writer) const { Base::OutputStream str(writer.Stream()); uint32_t uCt = (uint32_t)getSize(); str << uCt; for (float it : _lValueList) { str << it; } } void PropertyDistanceList::RestoreDocFile(Base::Reader& reader) { Base::InputStream str(reader); uint32_t uCt = 0; str >> uCt; std::vector values(uCt); for (float& it : values) { str >> it; } setValues(values); } App::Property* PropertyDistanceList::Copy() const { PropertyDistanceList* p = new PropertyDistanceList(); p->_lValueList = _lValueList; return p; } void PropertyDistanceList::Paste(const App::Property& from) { aboutToSetValue(); _lValueList = dynamic_cast(from)._lValueList; hasSetValue(); } unsigned int PropertyDistanceList::getMemSize() const { return static_cast(_lValueList.size() * sizeof(float)); } // ---------------------------------------------------------------- namespace Inspection { // helper class to use Qt's concurrent framework struct DistanceInspection { DistanceInspection(float radius, InspectActualGeometry* a, std::vector n) : radius(radius) , actual(a) , nominal(n) {} float mapped(unsigned long index) const { Base::Vector3f pnt = actual->getPoint(index); float fMinDist = std::numeric_limits::max(); for (auto it : nominal) { float fDist = it->getDistance(pnt); if (fabs(fDist) < fabs(fMinDist)) { fMinDist = fDist; } } if (fMinDist > this->radius) { fMinDist = std::numeric_limits::max(); } else if (-fMinDist > this->radius) { fMinDist = -std::numeric_limits::max(); } return fMinDist; } float radius; InspectActualGeometry* actual; std::vector nominal; }; // Helper internal class for QtConcurrent map operation. Holds sums-of-squares and counts for RMS // calculation class DistanceInspectionRMS { public: DistanceInspectionRMS() = default; DistanceInspectionRMS& operator+=(const DistanceInspectionRMS& rhs) { this->m_numv += rhs.m_numv; this->m_sumsq += rhs.m_sumsq; return *this; } double getRMS() { if (this->m_numv == 0) { return 0.0; } return sqrt(this->m_sumsq / (double)this->m_numv); } int m_numv {0}; double m_sumsq {0.0}; }; } // namespace Inspection PROPERTY_SOURCE(Inspection::Feature, App::DocumentObject) Feature::Feature() { ADD_PROPERTY(SearchRadius, (0.05)); ADD_PROPERTY(Thickness, (0.0)); ADD_PROPERTY(Actual, (nullptr)); ADD_PROPERTY(Nominals, (nullptr)); ADD_PROPERTY(Distances, (0.0)); } Feature::~Feature() = default; short Feature::mustExecute() const { if (SearchRadius.isTouched()) { return 1; } if (Thickness.isTouched()) { return 1; } if (Actual.isTouched()) { return 1; } if (Nominals.isTouched()) { return 1; } return 0; } App::DocumentObjectExecReturn* Feature::execute() { bool useMultithreading = true; App::DocumentObject* pcActual = Actual.getValue(); if (!pcActual) { throw Base::ValueError("No actual geometry to inspect specified"); } InspectActualGeometry* actual = nullptr; if (pcActual->isDerivedFrom()) { Mesh::Feature* mesh = static_cast(pcActual); actual = new InspectActualMesh(mesh->Mesh.getValue()); } else if (pcActual->isDerivedFrom()) { Points::Feature* pts = static_cast(pcActual); actual = new InspectActualPoints(pts->Points.getValue()); } else if (pcActual->isDerivedFrom()) { useMultithreading = false; Part::Feature* part = static_cast(pcActual); actual = new InspectActualShape(part->Shape.getShape()); } else { throw Base::TypeError("Unknown geometric type"); } // clang-format off // get a list of nominals std::vector inspectNominal; const std::vector& nominals = Nominals.getValues(); for (auto it : nominals) { InspectNominalGeometry* nominal = nullptr; if (it->isDerivedFrom()) { Mesh::Feature* mesh = static_cast(it); nominal = new InspectNominalMesh(mesh->Mesh.getValue(), this->SearchRadius.getValue()); } else if (it->isDerivedFrom()) { Points::Feature* pts = static_cast(it); nominal = new InspectNominalPoints(pts->Points.getValue(), this->SearchRadius.getValue()); } else if (it->isDerivedFrom()) { useMultithreading = false; Part::Feature* part = static_cast(it); nominal = new InspectNominalShape(part->Shape.getValue(), this->SearchRadius.getValue()); } if (nominal) { inspectNominal.push_back(nominal); } } // clang-format on #if 0 # if 1 // test with some huge data sets std::vector index(actual->countPoints()); std::generate(index.begin(), index.end(), Base::iotaGen(0)); DistanceInspection check(this->SearchRadius.getValue(), actual, inspectNominal); QFuture future = QtConcurrent::mapped (index, std::bind(&DistanceInspection::mapped, &check, sp::_1)); //future.waitForFinished(); // blocks the GUI Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints()); QFutureWatcher watcher; QObject::connect(&watcher, &QFutureWatcher::progressValueChanged, &progress, &Base::FutureWatcherProgress::progressValueChanged); // keep it responsive during computation QEventLoop loop; QObject::connect(&watcher, &QFutureWatcher::finished, &loop, &QEventLoop::quit); watcher.setFuture(future); loop.exec(); std::vector vals; vals.insert(vals.end(), future.begin(), future.end()); # else DistanceInspection insp(this->SearchRadius.getValue(), actual, inspectNominal); unsigned long count = actual->countPoints(); std::stringstream str; str << "Inspecting " << this->Label.getValue() << "…"; Base::SequencerLauncher seq(str.str().c_str(), count); std::vector vals(count); for (unsigned long index = 0; index < count; index++) { float fMinDist = insp.mapped(index); vals[index] = fMinDist; seq.next(); } # endif Distances.setValues(vals); float fRMS = 0; int countRMS = 0; for (std::vector::iterator it = vals.begin(); it != vals.end(); ++it) { if (fabs(*it) < std::numeric_limits::max()) { fRMS += (*it) * (*it); countRMS++; } } if (countRMS > 0) { fRMS = fRMS / countRMS; fRMS = sqrt(fRMS); } Base::Console().message("RMS value for '%s' with search radius [%.4f,%.4f] is: %.4f\n", this->Label.getValue(), -this->SearchRadius.getValue(), this->SearchRadius.getValue(), fRMS); #else unsigned long count = actual->countPoints(); std::vector vals(count); std::function fMap = [&](unsigned int index) { DistanceInspectionRMS res; Base::Vector3f pnt = actual->getPoint(index); float fMinDist = std::numeric_limits::max(); for (auto it : inspectNominal) { float fDist = it->getDistance(pnt); if (fabs(fDist) < fabs(fMinDist)) { fMinDist = fDist; } } if (fMinDist > this->SearchRadius.getValue()) { fMinDist = std::numeric_limits::max(); } else if (-fMinDist > this->SearchRadius.getValue()) { fMinDist = -std::numeric_limits::max(); } else { res.m_sumsq += static_cast(fMinDist) * static_cast(fMinDist); res.m_numv++; } vals[index] = fMinDist; return res; }; DistanceInspectionRMS res; if (useMultithreading) { // Build vector of increasing indices std::vector index(count); std::iota(index.begin(), index.end(), 0); // Perform map-reduce operation : compute distances and update sum of squares for RMS // computation QFuture future = QtConcurrent::mappedReduced(index, fMap, &DistanceInspectionRMS::operator+=); // Setup progress bar Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints()); QFutureWatcher watcher; QObject::connect( &watcher, &QFutureWatcher::progressValueChanged, &progress, &Base::FutureWatcherProgress::progressValueChanged ); // Keep UI responsive during computation QEventLoop loop; QObject::connect( &watcher, &QFutureWatcher::finished, &loop, &QEventLoop::quit ); watcher.setFuture(future); loop.exec(); res = future.result(); } else { // Single-threaded operation std::stringstream str; str << "Inspecting " << this->Label.getValue() << "…"; Base::SequencerLauncher seq(str.str().c_str(), count); for (unsigned int i = 0; i < count; i++) { res += fMap(i); } } Base::Console().message( "RMS value for '%s' with search radius [%.4f,%.4f] is: %.4f\n", this->Label.getValue(), -this->SearchRadius.getValue(), this->SearchRadius.getValue(), res.getRMS() ); Distances.setValues(vals); #endif delete actual; for (auto it : inspectNominal) { delete it; } return nullptr; } // ---------------------------------------------------------------- PROPERTY_SOURCE(Inspection::Group, App::DocumentObjectGroup) Group::Group() = default; Group::~Group() = default;