Download src/Mod/Sketcher/App/SketchAnalysis.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 33.9 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Sketcher/App/SketchAnalysis.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Sketcher/App/SketchAnalysis.cpp
-
curl -L -o SketchAnalysis.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Sketcher/App/SketchAnalysis.cpp
33.9 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /*************************************************************************** | |
| * Copyright (c) 2018 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> * | |
| * Copyright (c) 2013 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 Sketcher; | |
| SketchAnalysis::SketchAnalysis(Sketcher::SketchObject* Obj) | |
| : sketch(Obj) | |
| {} | |
| SketchAnalysis::~SketchAnalysis() = default; | |
| namespace | |
| { | |
| struct VertexIds | |
| { | |
| Base::Vector3d v; | |
| int GeoId {}; | |
| Sketcher::PointPos PosId {}; | |
| }; | |
| struct Vertex_Less | |
| { | |
| explicit Vertex_Less(double tolerance) | |
| : tolerance(tolerance) | |
| {} | |
| bool operator()(const VertexIds& x, const VertexIds& y) const | |
| { | |
| if (fabs(x.v.x - y.v.x) > tolerance) { | |
| return x.v.x < y.v.x; | |
| } | |
| if (fabs(x.v.y - y.v.y) > tolerance) { | |
| return x.v.y < y.v.y; | |
| } | |
| if (fabs(x.v.z - y.v.z) > tolerance) { | |
| return x.v.z < y.v.z; | |
| } | |
| return false; // points are considered to be equal | |
| } | |
| private: | |
| double tolerance; | |
| }; | |
| struct VertexID_Less | |
| { | |
| bool operator()(const VertexIds& x, const VertexIds& y) const | |
| { | |
| return (x.GeoId < y.GeoId || ((x.GeoId == y.GeoId) && (x.PosId < y.PosId))); | |
| } | |
| }; | |
| struct Vertex_EqualTo | |
| { | |
| explicit Vertex_EqualTo(double tolerance) | |
| : tolerance(tolerance) | |
| {} | |
| bool operator()(const VertexIds& x, const VertexIds& y) const | |
| { | |
| if (fabs(x.v.x - y.v.x) <= tolerance) { | |
| if (fabs(x.v.y - y.v.y) <= tolerance) { | |
| if (fabs(x.v.z - y.v.z) <= tolerance) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| private: | |
| double tolerance; | |
| }; | |
| struct EdgeIds | |
| { | |
| double l {}; | |
| int GeoId {}; | |
| }; | |
| struct Edge_Less | |
| { | |
| explicit Edge_Less(double tolerance) | |
| : tolerance(tolerance) | |
| {} | |
| bool operator()(const EdgeIds& x, const EdgeIds& y) const | |
| { | |
| if (fabs(x.l - y.l) > tolerance) { | |
| return x.l < y.l; | |
| } | |
| return false; // points are considered to be equal | |
| } | |
| private: | |
| double tolerance; | |
| }; | |
| struct Edge_EqualTo | |
| { | |
| explicit Edge_EqualTo(double tolerance) | |
| : tolerance(tolerance) | |
| {} | |
| bool operator()(const EdgeIds& x, const EdgeIds& y) const | |
| { | |
| return (fabs(x.l - y.l) <= tolerance); | |
| } | |
| private: | |
| double tolerance; | |
| }; | |
| struct PointConstraints | |
| { | |
| void addGeometry(const Part::Geometry* geo, int index) | |
| { | |
| if (const auto* segm = dynamic_cast<const Part::GeomLineSegment*>(geo)) { | |
| addLineSegment(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomArcOfCircle*>(geo)) { | |
| addArcOfCircle(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomArcOfEllipse*>(geo)) { | |
| addArcOfEllipse(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomArcOfHyperbola*>(geo)) { | |
| addArcOfHyperbola(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomArcOfParabola*>(geo)) { | |
| addArcOfParabola(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomBSplineCurve*>(geo)) { | |
| addBSplineCurve(segm, index); | |
| } | |
| } | |
| void addLineSegment(const Part::GeomLineSegment* segm, int index) | |
| { | |
| VertexIds id; | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::start; | |
| id.v = segm->getStartPoint(); | |
| vertexIds.push_back(id); | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::end; | |
| id.v = segm->getEndPoint(); | |
| vertexIds.push_back(id); | |
| } | |
| void addArcOfCircle(const Part::GeomArcOfCircle* segm, int index) | |
| { | |
| VertexIds id; | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::start; | |
| id.v = segm->getStartPoint(/*emulateCCWXY=*/true); | |
| vertexIds.push_back(id); | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::end; | |
| id.v = segm->getEndPoint(/*emulateCCWXY=*/true); | |
| vertexIds.push_back(id); | |
| } | |
| void addArcOfEllipse(const Part::GeomArcOfEllipse* segm, int index) | |
| { | |
| VertexIds id; | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::start; | |
| id.v = segm->getStartPoint(/*emulateCCWXY=*/true); | |
| vertexIds.push_back(id); | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::end; | |
| id.v = segm->getEndPoint(/*emulateCCWXY=*/true); | |
| vertexIds.push_back(id); | |
| } | |
| void addArcOfHyperbola(const Part::GeomArcOfHyperbola* segm, int index) | |
| { | |
| VertexIds id; | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::start; | |
| id.v = segm->getStartPoint(); | |
| vertexIds.push_back(id); | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::end; | |
| id.v = segm->getEndPoint(); | |
| vertexIds.push_back(id); | |
| } | |
| void addArcOfParabola(const Part::GeomArcOfParabola* segm, int index) | |
| { | |
| VertexIds id; | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::start; | |
| id.v = segm->getStartPoint(); | |
| vertexIds.push_back(id); | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::end; | |
| id.v = segm->getEndPoint(); | |
| vertexIds.push_back(id); | |
| } | |
| void addBSplineCurve(const Part::GeomBSplineCurve* segm, int index) | |
| { | |
| VertexIds id; | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::start; | |
| id.v = segm->getStartPoint(); | |
| vertexIds.push_back(id); | |
| id.GeoId = index; | |
| id.PosId = Sketcher::PointPos::end; | |
| id.v = segm->getEndPoint(); | |
| vertexIds.push_back(id); | |
| } | |
| std::list<ConstraintIds> getMissingCoincidences( | |
| std::vector<Sketcher::Constraint*>& allcoincid, | |
| double precision | |
| ) | |
| { | |
| std::list<ConstraintIds> missingCoincidences; // Holds the list of missing coincidences | |
| // Sort points in geographic order | |
| std::sort(vertexIds.begin(), vertexIds.end(), Vertex_Less(precision)); | |
| auto vt = vertexIds.begin(); | |
| Vertex_EqualTo pred(precision); | |
| // Comparing existing constraints and find missing ones | |
| while (vt < vertexIds.end()) { | |
| // Seeking for adjacent group of vertices | |
| vt = std::adjacent_find(vt, vertexIds.end(), pred); | |
| if (vt < vertexIds.end()) { // If one found | |
| std::vector<VertexIds>::iterator vn; | |
| // Holds a single group of adjacent vertices | |
| std::set<VertexIds, VertexID_Less> vertexGrp; | |
| // Extract the group of adjacent vertices | |
| vertexGrp.insert(*vt); | |
| for (vn = vt + 1; vn < vertexIds.end(); ++vn) { | |
| if (pred(*vt, *vn)) { | |
| vertexGrp.insert(*vn); | |
| } | |
| else { | |
| break; | |
| } | |
| } | |
| // Holds groups of coincident vertices | |
| std::vector<std::set<VertexIds, VertexID_Less>> coincVertexGrps; | |
| // Decompose the group of adjacent vertices into groups of coincident vertices | |
| // Going through existent coincidences | |
| for (auto& coincidence : allcoincid) { | |
| VertexIds v1; | |
| VertexIds v2; | |
| v1.GeoId = coincidence->First; | |
| v1.PosId = coincidence->FirstPos; | |
| v2.GeoId = coincidence->Second; | |
| v2.PosId = coincidence->SecondPos; | |
| // Look if coincident vertices are in the group of adjacent ones we are | |
| // processing | |
| auto nv1 = vertexGrp.extract(v1); | |
| auto nv2 = vertexGrp.extract(v2); | |
| // Maybe if both empty, they already have been extracted by other coincidences | |
| // We have to check in existing coincident groups and eventually merge | |
| if (nv1.empty() && nv2.empty()) { | |
| std::set<VertexIds, VertexID_Less>* tempGrp = nullptr; | |
| for (auto it = coincVertexGrps.begin(); it < coincVertexGrps.end(); ++it) { | |
| if ((it->find(v1) != it->end()) || (it->find(v2) != it->end())) { | |
| if (!tempGrp) { | |
| tempGrp = &*it; | |
| } | |
| else { | |
| tempGrp->insert(it->begin(), it->end()); | |
| coincVertexGrps.erase(it); | |
| break; | |
| } | |
| } | |
| } | |
| continue; | |
| } | |
| // Look if one of the constrained vertices is already in a group of coincident | |
| // vertices | |
| for (std::set<VertexIds, VertexID_Less>& grp : coincVertexGrps) { | |
| if ((grp.find(v1) != grp.end()) || (grp.find(v2) != grp.end())) { | |
| // If yes add them to the existing group | |
| if (!nv1.empty()) { | |
| grp.insert(nv1.value()); | |
| } | |
| if (!nv2.empty()) { | |
| grp.insert(nv2.value()); | |
| } | |
| continue; | |
| } | |
| } | |
| if (nv1.empty() || nv2.empty()) { | |
| continue; | |
| } | |
| // If no, create a new group of coincident vertices | |
| std::set<VertexIds, VertexID_Less> newGrp; | |
| newGrp.insert(nv1.value()); | |
| newGrp.insert(nv2.value()); | |
| coincVertexGrps.push_back(newGrp); | |
| } | |
| // If there are remaining vertices in the adjacent group (not in any existing | |
| // constraint) add them as being each a separate coincident group | |
| for (auto& lonept : vertexGrp) { | |
| std::set<VertexIds, VertexID_Less> newGrp; | |
| newGrp.insert(lonept); | |
| coincVertexGrps.push_back(newGrp); | |
| } | |
| // If there is more than 1 coincident group into adjacent group, constraint(s) | |
| // is(are) missing Virtually generate the missing constraint(s) | |
| if (coincVertexGrps.size() > 1) { | |
| std::vector<std::set<VertexIds, VertexID_Less>>::iterator vn; | |
| // Starting from the 2nd coincident group, generate a constraint between | |
| // this group first vertex, and previous group first vertex | |
| for (vn = coincVertexGrps.begin() + 1; vn < coincVertexGrps.end(); ++vn) { | |
| ConstraintIds id; | |
| id.Type = Coincident; // default point on point restriction | |
| id.v = (vn - 1)->begin()->v; | |
| id.First = (vn - 1)->begin()->GeoId; | |
| id.FirstPos = (vn - 1)->begin()->PosId; | |
| id.Second = vn->begin()->GeoId; | |
| id.SecondPos = vn->begin()->PosId; | |
| missingCoincidences.push_back(id); | |
| } | |
| } | |
| vt = vn; | |
| } | |
| } | |
| return missingCoincidences; | |
| } | |
| private: | |
| // Holds a list of all vertices in the sketch | |
| std::vector<VertexIds> vertexIds; | |
| }; | |
| struct EqualityConstraints | |
| { | |
| void addGeometry(const Part::Geometry* geo, int index) | |
| { | |
| if (const auto* segm = dynamic_cast<const Part::GeomLineSegment*>(geo)) { | |
| addLineSegment(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomArcOfCircle*>(geo)) { | |
| addArcOfCircle(segm, index); | |
| } | |
| else if (const auto* segm = dynamic_cast<const Part::GeomCircle*>(geo)) { | |
| addCircle(segm, index); | |
| } | |
| } | |
| void addLineSegment(const Part::GeomLineSegment* segm, int index) | |
| { | |
| EdgeIds id; | |
| id.GeoId = index; | |
| id.l = (segm->getEndPoint() - segm->getStartPoint()).Length(); | |
| lineedgeIds.push_back(id); | |
| } | |
| void addArcOfCircle(const Part::GeomArcOfCircle* segm, int index) | |
| { | |
| EdgeIds id; | |
| id.GeoId = index; | |
| id.l = segm->getRadius(); | |
| radiusedgeIds.push_back(id); | |
| } | |
| void addCircle(const Part::GeomCircle* segm, int index) | |
| { | |
| EdgeIds id; | |
| id.GeoId = index; | |
| id.l = segm->getRadius(); | |
| radiusedgeIds.push_back(id); | |
| } | |
| std::list<ConstraintIds> getEqualLines(double precision) | |
| { | |
| std::sort(lineedgeIds.begin(), lineedgeIds.end(), Edge_Less(precision)); | |
| auto vt = lineedgeIds.begin(); | |
| Edge_EqualTo pred(precision); | |
| std::list<ConstraintIds> equallines; | |
| // Make a list of constraint we expect for coincident vertexes | |
| while (vt < lineedgeIds.end()) { | |
| // get first item whose adjacent element has the same vertex coordinates | |
| vt = std::adjacent_find(vt, lineedgeIds.end(), pred); | |
| if (vt < lineedgeIds.end()) { | |
| std::vector<EdgeIds>::iterator vn; | |
| for (vn = vt + 1; vn != lineedgeIds.end(); ++vn) { | |
| if (pred(*vt, *vn)) { | |
| ConstraintIds id; | |
| id.Type = Equal; | |
| id.v.x = vt->l; | |
| id.First = vt->GeoId; | |
| id.FirstPos = Sketcher::PointPos::none; | |
| id.Second = vn->GeoId; | |
| id.SecondPos = Sketcher::PointPos::none; | |
| equallines.push_back(id); | |
| } | |
| else { | |
| break; | |
| } | |
| } | |
| vt = vn; | |
| } | |
| } | |
| return equallines; | |
| } | |
| std::list<ConstraintIds> getEqualRadius(double precision) | |
| { | |
| std::sort(radiusedgeIds.begin(), radiusedgeIds.end(), Edge_Less(precision)); | |
| auto vt = radiusedgeIds.begin(); | |
| Edge_EqualTo pred(precision); | |
| std::list<ConstraintIds> equalradius; | |
| // Make a list of constraint we expect for coincident vertexes | |
| while (vt < radiusedgeIds.end()) { | |
| // get first item whose adjacent element has the same vertex coordinates | |
| vt = std::adjacent_find(vt, radiusedgeIds.end(), pred); | |
| if (vt < radiusedgeIds.end()) { | |
| std::vector<EdgeIds>::iterator vn; | |
| for (vn = vt + 1; vn != radiusedgeIds.end(); ++vn) { | |
| if (pred(*vt, *vn)) { | |
| ConstraintIds id; | |
| id.Type = Equal; | |
| id.v.x = vt->l; | |
| id.First = vt->GeoId; | |
| id.FirstPos = Sketcher::PointPos::none; | |
| id.Second = vn->GeoId; | |
| id.SecondPos = Sketcher::PointPos::none; | |
| equalradius.push_back(id); | |
| } | |
| else { | |
| break; | |
| } | |
| } | |
| vt = vn; | |
| } | |
| } | |
| return equalradius; | |
| } | |
| private: | |
| std::vector<EdgeIds> lineedgeIds; | |
| std::vector<EdgeIds> radiusedgeIds; | |
| }; | |
| } // namespace | |
| int SketchAnalysis::detectMissingPointOnPointConstraints(double precision, bool includeconstruction /*=true*/) | |
| { | |
| PointConstraints pointConstr; | |
| // Build the list of sketch vertices | |
| const std::vector<Part::Geometry*>& geom = sketch->getInternalGeometry(); | |
| for (std::size_t i = 0; i < geom.size(); i++) { | |
| auto gf = GeometryFacade::getFacade(geom[i]); | |
| if (gf->getConstruction() && !includeconstruction) { | |
| continue; | |
| } | |
| pointConstr.addGeometry(gf->getGeometry(), int(i)); | |
| // TODO take into account single vertices ? | |
| } | |
| // Build a list of all coincidences in the sketch | |
| std::vector<Sketcher::Constraint*> coincidences = sketch->Constraints.getValues(); | |
| for (auto& constraint : sketch->Constraints.getValues()) { | |
| // clang-format off | |
| if (constraint->Type == Sketcher::Coincident || | |
| constraint->Type == Sketcher::Tangent || | |
| constraint->Type == Sketcher::Perpendicular) { | |
| coincidences.push_back(constraint); | |
| } | |
| // clang-format on | |
| // TODO optimizing by removing constraints not applying on vertices ? | |
| } | |
| // Holds the list of missing coincidences | |
| std::list<ConstraintIds> missingCoincidences | |
| = pointConstr.getMissingCoincidences(coincidences, precision); | |
| // Update list of missing constraints stored as member variable of sketch | |
| this->vertexConstraints.clear(); | |
| this->vertexConstraints.reserve(missingCoincidences.size()); | |
| for (auto& coincidence : missingCoincidences) { | |
| this->vertexConstraints.push_back(coincidence); | |
| } | |
| // Return number of missing constraints | |
| return int(this->vertexConstraints.size()); | |
| } | |
| void SketchAnalysis::analyseMissingPointOnPointCoincident(double angleprecision) | |
| { | |
| for (auto& vc : vertexConstraints) { | |
| auto geo1 = sketch->getGeometry(vc.First); | |
| auto geo2 = sketch->getGeometry(vc.Second); | |
| // tangency point-on-point | |
| const auto* curve1 = dynamic_cast<const Part::GeomCurve*>(geo1); | |
| const auto* curve2 = dynamic_cast<const Part::GeomCurve*>(geo2); | |
| if (curve1 && curve2) { | |
| const auto* segm1 = dynamic_cast<const Part::GeomLineSegment*>(geo1); | |
| const auto* segm2 = dynamic_cast<const Part::GeomLineSegment*>(geo2); | |
| if (segm1 && segm2) { | |
| Base::Vector3d dir1 = segm1->getEndPoint() - segm1->getStartPoint(); | |
| Base::Vector3d dir2 = segm2->getEndPoint() - segm2->getStartPoint(); | |
| if ((checkVertical(dir1, angleprecision) || checkHorizontal(dir1, angleprecision)) | |
| && (checkVertical(dir2, angleprecision) || checkHorizontal(dir2, angleprecision))) { | |
| // this is a job for horizontal/vertical constraints alone | |
| continue; | |
| } | |
| } | |
| try { | |
| double u1 {}; | |
| double u2 {}; | |
| curve1->closestParameter(vc.v, u1); | |
| curve2->closestParameter(vc.v, u2); | |
| Base::Vector3d tgv1 = curve1->firstDerivativeAtParameter(u1).Normalize(); | |
| Base::Vector3d tgv2 = curve2->firstDerivativeAtParameter(u2).Normalize(); | |
| if (fabs(tgv1 * tgv2) > fabs(cos(angleprecision))) { | |
| vc.Type = Sketcher::Tangent; | |
| } | |
| else if (fabs(tgv1 * tgv2) < fabs(cos(std::numbers::pi / 2 - angleprecision))) { | |
| vc.Type = Sketcher::Perpendicular; | |
| } | |
| } | |
| catch (Base::Exception&) { | |
| Base::Console().warning( | |
| "Point-On-Point Coincidence analysis: unable to obtain " | |
| "derivative. Detection ignored.\n" | |
| ); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| Sketcher::Constraint* SketchAnalysis::create(const ConstraintIds& id) | |
| { | |
| auto c = new Sketcher::Constraint(); | |
| c->Type = id.Type; | |
| c->First = id.First; | |
| c->Second = id.Second; | |
| c->FirstPos = id.FirstPos; | |
| c->SecondPos = id.SecondPos; | |
| return c; | |
| } | |
| void SketchAnalysis::solveSketch(const char* errorText) | |
| { | |
| int status {}; | |
| int dofs {}; | |
| solvesketch(status, dofs, true); | |
| if (status == int(Solver::RedundantConstraints)) { | |
| sketch->autoRemoveRedundants(DeleteOption::NoFlag); | |
| solvesketch(status, dofs, false); | |
| } | |
| if (status) { | |
| THROWMT(Base::RuntimeError, errorText); | |
| } | |
| } | |
| void SketchAnalysis::makeConstraints(std::vector<ConstraintIds>& ids) | |
| { | |
| std::vector<Sketcher::Constraint*> constr; | |
| constr.reserve(ids.size()); | |
| for (const auto& it : ids) { | |
| auto c = create(it); | |
| constr.push_back(c); | |
| } | |
| sketch->addConstraints(constr); | |
| ids.clear(); | |
| for (auto it : constr) { | |
| delete it; | |
| } | |
| } | |
| void SketchAnalysis::makeConstraintsOneByOne(std::vector<ConstraintIds>& ids, const char* errorText) | |
| { | |
| for (const auto& it : ids) { | |
| auto c = create(it); | |
| // addConstraint() creates a clone | |
| sketch->addConstraint(c); | |
| delete c; | |
| solveSketch(errorText); | |
| } | |
| ids.clear(); | |
| } | |
| void SketchAnalysis::makeMissingPointOnPointCoincident() | |
| { | |
| makeConstraints(vertexConstraints); | |
| } | |
| void SketchAnalysis::makeMissingPointOnPointCoincidentOneByOne() | |
| { | |
| makeConstraintsOneByOne( | |
| vertexConstraints, | |
| QT_TRANSLATE_NOOP( | |
| "Exceptions", | |
| "Autoconstraint error: Unsolvable sketch while " | |
| "applying coincident constraints." | |
| ) | |
| ); | |
| } | |
| int SketchAnalysis::detectMissingVerticalHorizontalConstraints(double angleprecision) | |
| { | |
| const std::vector<Part::Geometry*>& geom = sketch->getInternalGeometry(); | |
| verthorizConstraints.clear(); | |
| for (std::size_t i = 0; i < geom.size(); i++) { | |
| Part::Geometry* g = geom[i]; | |
| if (const auto* segm = dynamic_cast<const Part::GeomLineSegment*>(g)) { | |
| Base::Vector3d dir = segm->getEndPoint() - segm->getStartPoint(); | |
| ConstraintIds id; | |
| id.v = dir; | |
| id.First = (int)i; | |
| id.FirstPos = Sketcher::PointPos::none; | |
| id.Second = GeoEnum::GeoUndef; | |
| id.SecondPos = Sketcher::PointPos::none; | |
| if (checkVertical(dir, angleprecision)) { | |
| id.Type = Sketcher::Vertical; | |
| verthorizConstraints.push_back(id); | |
| } | |
| else if (checkHorizontal(dir, angleprecision)) { | |
| id.Type = Sketcher::Horizontal; | |
| verthorizConstraints.push_back(id); | |
| } | |
| } | |
| } | |
| return int(verthorizConstraints.size()); | |
| } | |
| void SketchAnalysis::makeMissingVerticalHorizontal() | |
| { | |
| makeConstraints(verthorizConstraints); | |
| } | |
| void SketchAnalysis::makeMissingVerticalHorizontalOneByOne() | |
| { | |
| makeConstraintsOneByOne( | |
| verthorizConstraints, | |
| QT_TRANSLATE_NOOP( | |
| "Exceptions", | |
| "Autoconstraint error: Unsolvable sketch while " | |
| "applying vertical/horizontal constraints." | |
| ) | |
| ); | |
| } | |
| bool SketchAnalysis::checkVertical(Base::Vector3d dir, double angleprecision) | |
| { | |
| return (dir.x == 0. && dir.y != 0.) | |
| || (fabs(dir.y / dir.x) > tan(std::numbers::pi / 2 - angleprecision)); | |
| } | |
| bool SketchAnalysis::checkHorizontal(Base::Vector3d dir, double angleprecision) | |
| { | |
| return (dir.y == 0. && dir.x != 0.) || (fabs(dir.x / dir.y) > (1 / tan(angleprecision))); | |
| } | |
| int SketchAnalysis::detectMissingEqualityConstraints(double precision) | |
| { | |
| EqualityConstraints equalConstr; | |
| const std::vector<Part::Geometry*>& geom = sketch->getInternalGeometry(); | |
| for (std::size_t i = 0; i < geom.size(); i++) { | |
| Part::Geometry* g = geom[i]; | |
| equalConstr.addGeometry(g, int(i)); | |
| } | |
| std::list<ConstraintIds> equallines = equalConstr.getEqualLines(precision); | |
| std::list<ConstraintIds> equalradius = equalConstr.getEqualRadius(precision); | |
| // Go through the available 'Coincident', 'Tangent' or 'Perpendicular' constraints | |
| // and check which of them is forcing two vertexes to be coincident. | |
| // If there is none but two vertexes can be considered equal a coincident constraint is missing. | |
| std::vector<Sketcher::Constraint*> constraint = sketch->Constraints.getValues(); | |
| for (auto it : constraint) { | |
| if (it->Type == Sketcher::Equal) { | |
| ConstraintIds | |
| id {Base::Vector3d {}, it->First, it->Second, it->FirstPos, it->SecondPos, it->Type}; | |
| auto pos = std::find_if(equallines.begin(), equallines.end(), Constraint_Equal(id)); | |
| if (pos != equallines.end()) { | |
| equallines.erase(pos); | |
| } | |
| pos = std::find_if(equalradius.begin(), equalradius.end(), Constraint_Equal(id)); | |
| if (pos != equalradius.end()) { | |
| equalradius.erase(pos); | |
| } | |
| } | |
| } | |
| this->lineequalityConstraints.clear(); | |
| this->lineequalityConstraints.reserve(equallines.size()); | |
| for (const auto& it : equallines) { | |
| this->lineequalityConstraints.push_back(it); | |
| } | |
| this->radiusequalityConstraints.clear(); | |
| this->radiusequalityConstraints.reserve(equalradius.size()); | |
| for (const auto& it : equalradius) { | |
| this->radiusequalityConstraints.push_back(it); | |
| } | |
| return int(this->lineequalityConstraints.size() + this->radiusequalityConstraints.size()); | |
| } | |
| void SketchAnalysis::makeMissingEquality() | |
| { | |
| std::vector<Sketcher::ConstraintIds> equalities(lineequalityConstraints); | |
| equalities.insert( | |
| equalities.end(), | |
| radiusequalityConstraints.begin(), | |
| radiusequalityConstraints.end() | |
| ); | |
| makeConstraints(equalities); | |
| lineequalityConstraints.clear(); | |
| radiusequalityConstraints.clear(); | |
| } | |
| void SketchAnalysis::makeMissingEqualityOneByOne() | |
| { | |
| std::vector<Sketcher::ConstraintIds> equalities(lineequalityConstraints); | |
| equalities.insert( | |
| equalities.end(), | |
| radiusequalityConstraints.begin(), | |
| radiusequalityConstraints.end() | |
| ); | |
| makeConstraintsOneByOne( | |
| equalities, | |
| QT_TRANSLATE_NOOP( | |
| "Exceptions", | |
| "Autoconstraint error: Unsolvable sketch while " | |
| "applying equality constraints." | |
| ) | |
| ); | |
| lineequalityConstraints.clear(); | |
| radiusequalityConstraints.clear(); | |
| } | |
| void SketchAnalysis::solvesketch(int& status, int& dofs, bool updategeo) | |
| { | |
| status = sketch->solve(updategeo); | |
| if (updategeo) { | |
| dofs = sketch->setUpSketch(); | |
| } | |
| else { | |
| dofs = sketch->getLastDoF(); | |
| } | |
| if (sketch->getLastHasRedundancies()) { | |
| status = int(Solver::RedundantConstraints); | |
| } | |
| if (dofs < 0) { | |
| status = int(Solver::OverConstrained); | |
| } | |
| else if (sketch->getLastHasConflicts()) { | |
| status = int(Solver::ConflictingConstraints); | |
| } | |
| } | |
| void SketchAnalysis::autoDeleteAllConstraints() | |
| { | |
| App::Document* doc = sketch->getDocument(); | |
| doc->openTransaction("delete all constraints"); | |
| // We start from zero | |
| sketch->deleteAllConstraints(); | |
| doc->commitTransaction(); | |
| // a failure should not be possible at this moment as we start from a clean situation | |
| solveSketch( | |
| QT_TRANSLATE_NOOP("Exceptions", "Autoconstraint error: Unsolvable sketch without constraints.") | |
| ); | |
| } | |
| void SketchAnalysis::autoHorizontalVerticalConstraints() | |
| { | |
| App::Document* doc = sketch->getDocument(); | |
| doc->openTransaction("add vertical/horizontal constraints"); | |
| makeMissingVerticalHorizontal(); | |
| // finish the transaction and update | |
| doc->commitTransaction(); | |
| solveSketch(QT_TRANSLATE_NOOP( | |
| "Exceptions", | |
| "Autoconstraint error: Unsolvable sketch after applying " | |
| "horizontal and vertical constraints." | |
| )); | |
| } | |
| void SketchAnalysis::autoPointOnPointCoincident() | |
| { | |
| App::Document* doc = sketch->getDocument(); | |
| doc->openTransaction("add coincident constraint"); | |
| makeMissingPointOnPointCoincident(); | |
| // finish the transaction and update | |
| doc->commitTransaction(); | |
| solveSketch(QT_TRANSLATE_NOOP( | |
| "Exceptions", | |
| "Autoconstraint error: Unsolvable sketch after applying " | |
| "point-on-point constraints." | |
| )); | |
| } | |
| void SketchAnalysis::autoMissingEquality() | |
| { | |
| App::Document* doc = sketch->getDocument(); | |
| doc->openTransaction("add equality constraints"); | |
| try { | |
| makeMissingEquality(); | |
| } | |
| catch (Base::RuntimeError&) { | |
| doc->abortTransaction(); | |
| throw; | |
| } | |
| // finish the transaction and update | |
| doc->commitTransaction(); | |
| solveSketch(QT_TRANSLATE_NOOP( | |
| "Exceptions", | |
| "Autoconstraint error: Unsolvable sketch after " | |
| "applying equality constraints." | |
| )); | |
| } | |
| int SketchAnalysis::autoconstraint(double precision, double angleprecision, bool includeconstruction) | |
| { | |
| autoDeleteAllConstraints(); | |
| // STAGE 1: Vertical/Horizontal Line Segments | |
| int nhv = detectMissingVerticalHorizontalConstraints(angleprecision); | |
| // STAGE 2: Point-on-Point constraint (Coincidents, endpoint perp, endpoint tangency) | |
| // Note: We do not apply the vertical/horizontal constraints before calculating the pointonpoint | |
| // constraints | |
| // as the solver may move the geometry in the meantime and prevent correct detection | |
| int nc = detectMissingPointOnPointConstraints(precision, includeconstruction); | |
| if (nc > 0) { // STAGE 2a: Classify point-on-point into coincidents, endpoint perp, endpoint | |
| // tangency | |
| analyseMissingPointOnPointCoincident(angleprecision); | |
| } | |
| // STAGE 3: Equality constraint detection | |
| int ne = detectMissingEqualityConstraints(precision); | |
| Base::Console().log( | |
| "Constraints: Vertical/Horizontal: %d found. " | |
| "Point-on-point: %d. Equality: %d\n", | |
| nhv, | |
| nc, | |
| ne | |
| ); | |
| // Applying STAGE 1, if any | |
| if (nhv > 0) { | |
| autoHorizontalVerticalConstraints(); | |
| } | |
| // Applying STAGE 2 | |
| if (nc > 0) { | |
| autoPointOnPointCoincident(); | |
| } | |
| // Applying STAGE 3 | |
| if (ne > 0) { | |
| autoMissingEquality(); | |
| } | |
| return 0; | |
| } | |
| std::vector<Base::Vector3d> SketchAnalysis::getOpenVertices() const | |
| { | |
| std::vector<Base::Vector3d> points; | |
| TopoDS_Shape shape = sketch->Shape.getValue(); | |
| Base::Placement Plm = sketch->Placement.getValue(); | |
| Base::Placement invPlm = Plm.inverse(); | |
| // build up map vertex->edge | |
| TopTools_IndexedDataMapOfShapeListOfShape vertex2Edge; | |
| TopExp::MapShapesAndAncestors(shape, TopAbs_VERTEX, TopAbs_EDGE, vertex2Edge); | |
| for (int i = 1; i <= vertex2Edge.Extent(); ++i) { | |
| const TopTools_ListOfShape& los = vertex2Edge.FindFromIndex(i); | |
| if (los.Extent() != 2) { | |
| const TopoDS_Vertex& vertex = TopoDS::Vertex(vertex2Edge.FindKey(i)); | |
| gp_Pnt pnt = BRep_Tool::Pnt(vertex); | |
| Base::Vector3d pos; | |
| invPlm.multVec(Base::Vector3d(pnt.X(), pnt.Y(), pnt.Z()), pos); | |
| points.push_back(pos); | |
| } | |
| } | |
| return points; | |
| } | |
| std::set<int> SketchAnalysis::getDegeneratedGeometries(double tolerance) const | |
| { | |
| std::set<int> delInternalGeometries; | |
| const std::vector<Part::Geometry*>& geom = sketch->getInternalGeometry(); | |
| for (std::size_t i = 0; i < geom.size(); i++) { | |
| auto gf = GeometryFacade::getFacade(geom[i]); | |
| if (gf->getConstruction()) { | |
| continue; | |
| } | |
| if (auto curve = dynamic_cast<Part::GeomCurve*>(gf->getGeometry())) { | |
| double len = curve->length(curve->getFirstParameter(), curve->getLastParameter()); | |
| if (len < tolerance) { | |
| delInternalGeometries.insert(static_cast<int>(i)); | |
| } | |
| } | |
| } | |
| return delInternalGeometries; | |
| } | |
| int SketchAnalysis::detectDegeneratedGeometries(double tolerance) const | |
| { | |
| std::set<int> delInternalGeometries = getDegeneratedGeometries(tolerance); | |
| return static_cast<int>(delInternalGeometries.size()); | |
| } | |
| int SketchAnalysis::removeDegeneratedGeometries(double tolerance) | |
| { | |
| std::set<int> delInternalGeometries = getDegeneratedGeometries(tolerance); | |
| for (auto it = delInternalGeometries.rbegin(); it != delInternalGeometries.rend(); ++it) { | |
| sketch->delGeometry(*it); | |
| } | |
| return static_cast<int>(delInternalGeometries.size()); | |
| } | |