Download src/Mod/Inspection/App/InspectionFeature.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 32 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Inspection/App/InspectionFeature.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Inspection/App/InspectionFeature.cpp
-
curl -L -o InspectionFeature.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Inspection/App/InspectionFeature.cpp
32 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /*************************************************************************** | |
| * Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> * | |
| * * | |
| * 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 * | |
| * * | |
| ***************************************************************************/ | |
| 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<Data::ComplexGeoData::Facet> faces; | |
| _rShape.getFaces(points, faces, deflection); | |
| } | |
| else { | |
| TopExp::MapShapes(_rShape.getShape(), TopAbs_EDGE, mapOfShapes); | |
| if (!mapOfShapes.IsEmpty()) { | |
| std::vector<Data::ComplexGeoData::Line> lines; | |
| _rShape.getLines(points, lines, deflection); | |
| } | |
| else { | |
| std::vector<Base::Vector3d> 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<float>(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>((unsigned long)(clBBMesh.LengthX() / fGridLen), 1), | |
| std::max<unsigned long>((unsigned long)(clBBMesh.LengthY() / fGridLen), 1), | |
| std::max<unsigned long>((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<float>(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<float>::max(); // must be inside bbox | |
| } | |
| std::vector<unsigned long> indices; | |
| //_pGrid->GetElements(point, indices); | |
| if (indices.empty()) { | |
| std::set<unsigned long> inds; | |
| _pGrid->MeshGrid::SearchNearestFromPoint(point, inds); | |
| indices.insert(indices.begin(), inds.begin(), inds.end()); | |
| } | |
| float fMinDist = std::numeric_limits<float>::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<float>(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<float>::max(); // must be inside bbox | |
| } | |
| std::set<unsigned long> indices; | |
| std::vector<unsigned long> elements; | |
| _pGrid->GetElements(point, elements); | |
| indices.insert(elements.begin(), elements.end()); | |
| 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); | |
| } | |
| float fMinDist = std::numeric_limits<float>::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<unsigned long> 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<double>::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<float>::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<int>(_lValueList.size()); | |
| } | |
| void PropertyDistanceList::setValue(float lValue) | |
| { | |
| aboutToSetValue(); | |
| _lValueList.resize(1); | |
| _lValueList[0] = lValue; | |
| hasSetValue(); | |
| } | |
| void PropertyDistanceList::setValues(const std::vector<float>& 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<float> 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() << "<FloatList count=\"" << getSize() << "\">" << std::endl; | |
| writer.incInd(); | |
| for (int i = 0; i < getSize(); i++) { | |
| writer.Stream() << writer.ind() << "<F v=\"" << _lValueList[i] << "\"/>" << std::endl; | |
| } | |
| writer.decInd(); | |
| writer.Stream() << writer.ind() << "</FloatList>" << std::endl; | |
| } | |
| else { | |
| writer.Stream() << writer.ind() << "<FloatList file=\"" << writer.addFile(getName(), this) | |
| << "\"/>" << std::endl; | |
| } | |
| } | |
| void PropertyDistanceList::Restore(Base::XMLReader& reader) | |
| { | |
| reader.readElement("FloatList"); | |
| std::string file(reader.getAttribute<const char*>("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<float> 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<const PropertyDistanceList&>(from)._lValueList; | |
| hasSetValue(); | |
| } | |
| unsigned int PropertyDistanceList::getMemSize() const | |
| { | |
| return static_cast<unsigned int>(_lValueList.size() * sizeof(float)); | |
| } | |
| // ---------------------------------------------------------------- | |
| namespace Inspection | |
| { | |
| // helper class to use Qt's concurrent framework | |
| struct DistanceInspection | |
| { | |
| DistanceInspection(float radius, InspectActualGeometry* a, std::vector<InspectNominalGeometry*> n) | |
| : radius(radius) | |
| , actual(a) | |
| , nominal(n) | |
| {} | |
| float mapped(unsigned long index) const | |
| { | |
| Base::Vector3f pnt = actual->getPoint(index); | |
| float fMinDist = std::numeric_limits<float>::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<float>::max(); | |
| } | |
| else if (-fMinDist > this->radius) { | |
| fMinDist = -std::numeric_limits<float>::max(); | |
| } | |
| return fMinDist; | |
| } | |
| float radius; | |
| InspectActualGeometry* actual; | |
| std::vector<InspectNominalGeometry*> 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::Feature* mesh = static_cast<Mesh::Feature*>(pcActual); | |
| actual = new InspectActualMesh(mesh->Mesh.getValue()); | |
| } | |
| else if (pcActual->isDerivedFrom<Points::Feature>()) { | |
| Points::Feature* pts = static_cast<Points::Feature*>(pcActual); | |
| actual = new InspectActualPoints(pts->Points.getValue()); | |
| } | |
| else if (pcActual->isDerivedFrom<Part::Feature>()) { | |
| useMultithreading = false; | |
| Part::Feature* part = static_cast<Part::Feature*>(pcActual); | |
| actual = new InspectActualShape(part->Shape.getShape()); | |
| } | |
| else { | |
| throw Base::TypeError("Unknown geometric type"); | |
| } | |
| // clang-format off | |
| // get a list of nominals | |
| std::vector<InspectNominalGeometry*> inspectNominal; | |
| const std::vector<App::DocumentObject*>& nominals = Nominals.getValues(); | |
| for (auto it : nominals) { | |
| InspectNominalGeometry* nominal = nullptr; | |
| if (it->isDerivedFrom<Mesh::Feature>()) { | |
| Mesh::Feature* mesh = static_cast<Mesh::Feature*>(it); | |
| nominal = new InspectNominalMesh(mesh->Mesh.getValue(), this->SearchRadius.getValue()); | |
| } | |
| else if (it->isDerivedFrom<Points::Feature>()) { | |
| Points::Feature* pts = static_cast<Points::Feature*>(it); | |
| nominal = new InspectNominalPoints(pts->Points.getValue(), this->SearchRadius.getValue()); | |
| } | |
| else if (it->isDerivedFrom<Part::Feature>()) { | |
| useMultithreading = false; | |
| Part::Feature* part = static_cast<Part::Feature*>(it); | |
| nominal = new InspectNominalShape(part->Shape.getValue(), this->SearchRadius.getValue()); | |
| } | |
| if (nominal) { | |
| inspectNominal.push_back(nominal); | |
| } | |
| } | |
| // clang-format on | |
| std::vector<unsigned long> index(actual->countPoints()); | |
| std::generate(index.begin(), index.end(), Base::iotaGen<unsigned long>(0)); | |
| DistanceInspection check(this->SearchRadius.getValue(), actual, inspectNominal); | |
| QFuture<float> future = QtConcurrent::mapped | |
| (index, std::bind(&DistanceInspection::mapped, &check, sp::_1)); | |
| //future.waitForFinished(); // blocks the GUI | |
| Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints()); | |
| QFutureWatcher<float> watcher; | |
| QObject::connect(&watcher, &QFutureWatcher<float>::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<float> vals; | |
| vals.insert(vals.end(), future.begin(), future.end()); | |
| 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<float> vals(count); | |
| for (unsigned long index = 0; index < count; index++) { | |
| float fMinDist = insp.mapped(index); | |
| vals[index] = fMinDist; | |
| seq.next(); | |
| } | |
| Distances.setValues(vals); | |
| float fRMS = 0; | |
| int countRMS = 0; | |
| for (std::vector<float>::iterator it = vals.begin(); it != vals.end(); ++it) { | |
| if (fabs(*it) < std::numeric_limits<float>::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); | |
| unsigned long count = actual->countPoints(); | |
| std::vector<float> vals(count); | |
| std::function<DistanceInspectionRMS(int)> fMap = [&](unsigned int index) { | |
| DistanceInspectionRMS res; | |
| Base::Vector3f pnt = actual->getPoint(index); | |
| float fMinDist = std::numeric_limits<float>::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<float>::max(); | |
| } | |
| else if (-fMinDist > this->SearchRadius.getValue()) { | |
| fMinDist = -std::numeric_limits<float>::max(); | |
| } | |
| else { | |
| res.m_sumsq += static_cast<double>(fMinDist) * static_cast<double>(fMinDist); | |
| res.m_numv++; | |
| } | |
| vals[index] = fMinDist; | |
| return res; | |
| }; | |
| DistanceInspectionRMS res; | |
| if (useMultithreading) { | |
| // Build vector of increasing indices | |
| std::vector<unsigned long> index(count); | |
| std::iota(index.begin(), index.end(), 0); | |
| // Perform map-reduce operation : compute distances and update sum of squares for RMS | |
| // computation | |
| QFuture<DistanceInspectionRMS> future | |
| = QtConcurrent::mappedReduced(index, fMap, &DistanceInspectionRMS::operator+=); | |
| // Setup progress bar | |
| Base::FutureWatcherProgress progress("Inspecting...", actual->countPoints()); | |
| QFutureWatcher<DistanceInspectionRMS> watcher; | |
| QObject::connect( | |
| &watcher, | |
| &QFutureWatcher<DistanceInspectionRMS>::progressValueChanged, | |
| &progress, | |
| &Base::FutureWatcherProgress::progressValueChanged | |
| ); | |
| // Keep UI responsive during computation | |
| QEventLoop loop; | |
| QObject::connect( | |
| &watcher, | |
| &QFutureWatcher<DistanceInspectionRMS>::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); | |
| delete actual; | |
| for (auto it : inspectNominal) { | |
| delete it; | |
| } | |
| return nullptr; | |
| } | |
| // ---------------------------------------------------------------- | |
| PROPERTY_SOURCE(Inspection::Group, App::DocumentObjectGroup) | |
| Group::Group() = default; | |
| Group::~Group() = default; | |