/*************************************************************************** * 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 "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "InspectionFeature.h" using namespace Inspection; namespace bp = boost::placeholders; InspectActualMesh::InspectActualMesh(const Mesh::MeshObject& rMesh) : _mesh(rMesh.getKernel()) { Base::Matrix4D tmp; _clTrf = rMesh.getTransform(); _bApply = _clTrf != tmp; } InspectActualMesh::~InspectActualMesh() { } 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 p = _rKernel.getPoint(index); return Base::Vector3f(float(p.x), float(p.y), float(p.z)); } // ---------------------------------------------------------------- InspectActualShape::InspectActualShape(const Part::TopoShape& shape) : _rShape(shape) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/Mod/Part"); float deviation = hGrp->GetFloat("MeshDeviation",0.2); Base::BoundBox3d bbox = _rShape.getBoundBox(); Standard_Real deflection = (bbox.LengthX() + bbox.LengthY() + bbox.LengthZ())/300.0 * deviation; std::vector f; _rShape.getFaces(points, f, (float)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& m) : MeshCore::MeshGrid(mesh), _transform(m) { Base::BoundBox3f clBBMesh = _pclMesh->GetBoundBox().Transformed(m); 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&) { // do nothing } void Validate (void) { // do nothing } bool Verify() const { // do nothing return true; } protected: void CalculateGridLength (unsigned long /*ulCtGrid*/, unsigned long /*ulMaxGrids*/) { // do nothing } void CalculateGridLength (int /*iCtGridPerAxis*/) { // do nothing } unsigned long HasElements (void) const { 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 ulX, ulY, ulZ; unsigned long ulX1, ulY1, ulZ1, ulX2, ulY2, 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 (ulX = ulX1; ulX <= ulX2; ulX++) { for (ulY = ulY1; ulY <= ulY2; ulY++) { for (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 (void) { 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 (void) { _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; }; } 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 FLT_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=FLT_MAX; bool positive = true; for (std::vector::iterator it = indices.begin(); it != indices.end(); ++it) { 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 FLT_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.size() == 0 && ulLevel <= max_level) _pGrid->GetHull(ulX, ulY, ulZ, ulLevel++, indices); if (indices.size() == 0 || ulLevel==1) _pGrid->GetHull(ulX, ulY, ulZ, ulLevel, indices); #endif float fMinDist=FLT_MAX; bool positive = true; for (std::set::iterator it = indices.begin(); it != indices.end(); ++it) { 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=DBL_MAX; for (std::set::iterator it = indices.begin(); it != indices.end(); ++it) { 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) , isSolid(false) { 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=FLT_MAX; if (distss->Perform() && distss->NbSolution() > 0) { fMinDist = (float)distss->Value(); // the shape is a solid, check if the vertex is inside if (isSolid) { const Standard_Real tol = 0.001; BRepClass3d_SolidClassifier classifier(_rShape); classifier.Perform(pnt3d, tol); if (classifier.State() == TopAbs_IN) { fMinDist = -fMinDist; } } else if (fMinDist > 0) { // check if the distance was compued 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) { fMinDist = -fMinDist; } break; } } } } return fMinDist; } // ---------------------------------------------------------------- TYPESYSTEM_SOURCE(Inspection::PropertyDistanceList, App::PropertyLists) PropertyDistanceList::PropertyDistanceList() { } PropertyDistanceList::~PropertyDistanceList() { } void PropertyDistanceList::setSize(int newSize) { _lValueList.resize(newSize); } int PropertyDistanceList::getSize(void) 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(void) { PyObject* list = PyList_New(getSize()); for (int i = 0;i values; values.resize(nSize); for (Py_ssize_t i=0; iob_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" << 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 (std::vector::const_iterator it = _lValueList.begin(); it != _lValueList.end(); ++it) { str << *it; } } void PropertyDistanceList::RestoreDocFile(Base::Reader &reader) { Base::InputStream str(reader); uint32_t uCt=0; str >> uCt; std::vector values(uCt); for (std::vector::iterator it = values.begin(); it != values.end(); ++it) { str >> *it; } setValues(values); } App::Property *PropertyDistanceList::Copy(void) 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 (void) 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=FLT_MAX; for (std::vector::const_iterator it = nominal.begin(); it != nominal.end(); ++it) { float fDist = (*it)->getDistance(pnt); if (fabs(fDist) < fabs(fMinDist)) fMinDist = fDist; } if (fMinDist > this->radius) fMinDist = FLT_MAX; else if (-fMinDist > this->radius) fMinDist = -FLT_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() : m_numv(0), m_sumsq(0.0) {} DistanceInspectionRMS& operator += (const DistanceInspectionRMS& rhs) { this->m_numv += rhs.m_numv; this->m_sumsq += rhs.m_sumsq; return *this; } double getRMS() { return sqrt(this->m_sumsq / (double)this->m_numv); } int m_numv; double m_sumsq; }; } 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() { } 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(void) { bool useMultithreading = true; App::DocumentObject* pcActual = Actual.getValue(); if (!pcActual) throw Base::ValueError("No actual geometry to inspect specified"); InspectActualGeometry* actual = nullptr; if (pcActual->getTypeId().isDerivedFrom(Mesh::Feature::getClassTypeId())) { Mesh::Feature* mesh = static_cast(pcActual); actual = new InspectActualMesh(mesh->Mesh.getValue()); } else if (pcActual->getTypeId().isDerivedFrom(Points::Feature::getClassTypeId())) { Points::Feature* pts = static_cast(pcActual); actual = new InspectActualPoints(pts->Points.getValue()); } else if (pcActual->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { useMultithreading = false; Part::Feature* part = static_cast(pcActual); actual = new InspectActualShape(part->Shape.getShape()); } else { throw Base::TypeError("Unknown geometric type"); } // get a list of nominals std::vector inspectNominal; const std::vector& nominals = Nominals.getValues(); for (std::vector::const_iterator it = nominals.begin(); it != nominals.end(); ++it) { InspectNominalGeometry* nominal = nullptr; if ((*it)->getTypeId().isDerivedFrom(Mesh::Feature::getClassTypeId())) { Mesh::Feature* mesh = static_cast(*it); nominal = new InspectNominalMesh(mesh->Mesh.getValue(), this->SearchRadius.getValue()); } else if ((*it)->getTypeId().isDerivedFrom(Points::Feature::getClassTypeId())) { Points::Feature* pts = static_cast(*it); nominal = new InspectNominalPoints(pts->Points.getValue(), this->SearchRadius.getValue()); } else if ((*it)->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { useMultithreading = false; Part::Feature* part = static_cast(*it); nominal = new InspectNominalShape(part->Shape.getValue(), this->SearchRadius.getValue()); } if (nominal) inspectNominal.push_back(nominal); } #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, boost::bind(&DistanceInspection::mapped, &check, bp::_1)); //future.waitForFinished(); // blocks the GUI Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints()); QFutureWatcher watcher; QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &progress, SLOT(progressValueChanged(int))); // keep it responsive during computation QEventLoop loop; QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(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) < FLT_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 = FLT_MAX; for (std::vector::iterator it = inspectNominal.begin(); it != inspectNominal.end(); ++it) { float fDist = (*it)->getDistance(pnt); if (fabs(fDist) < fabs(fMinDist)) fMinDist = fDist; } if (fMinDist > this->SearchRadius.getValue()) { fMinDist = FLT_MAX; } else if (-fMinDist > this->SearchRadius.getValue()) { fMinDist = -FLT_MAX; } else { res.m_sumsq += fMinDist * 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, SIGNAL(progressValueChanged(int)), &progress, SLOT(progressValueChanged(int))); // Keep UI responsive during computation QEventLoop loop; QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(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 (std::vector::iterator it = inspectNominal.begin(); it != inspectNominal.end(); ++it) delete *it; return nullptr; } // ---------------------------------------------------------------- PROPERTY_SOURCE(Inspection::Group, App::DocumentObjectGroup) Group::Group() { } Group::~Group() { }