From 97f375296ba9bb09f3c282b418aea99a402552af Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 9 Jul 2018 14:16:22 +0200 Subject: [PATCH] Sketcher: Sketch Analysis tool and autoconstraining algorithms A series of algorithms to detect missing constraints and create them. Each of the algorithms is divided in different steps for maximum flexibility: /// There is a first type of routines, simple routines, which work in the following order: /// Detect - (Analyse) - [Get] - [Set] - Make /// /// The Detect step just identifies possible missing constraints. /// /// The Analyse, which is not available for all the routines, operates in detected constraints of the same routine, to /// look for alternatives. For example, a general pointonpoint detection leads to a search for coincident constraints, which /// can be later run via Analyse if it is intended to convert endpoint coincidence to endpoint perpendicular and tangent constraints. /// /// The Get retrieves the result of the analysis as a vector of ConstraintIds, indicating the suggested constraints. This step is intended /// for enabling the user to check the result of the analysis, rather than applying it. If only applying is intended, this step is not necessary /// as the Make will operate on the result of the Detect - Analyse directly. /// /// The Set changes the detected result. It modifies the SketchAnalysis object. It only modifies the SketchObject as far as the SketchAnalysis is changed. /// It does not apply any changes to the sketch. It is intended so as to enable the user to change the result that will be applied. /// /// Neither the Detect, nor the Analyse, nor the Get steps modify the Sketch geometry. /// /// Make applies the constraints stored internally in the SketchAnalysis object. It includes an automatic constraining algorithm for coincidences, horizontals/verticals and equality: /// A second type of routines, complex routines, are thought for running fully automatic and they Detect, Analyse and Make. /// They may also apply a variaty of types of Constraints. It also includes some helper functions, like autoRemoveRedundants --- src/Mod/Sketcher/App/Analyse.h | 62 ++ src/Mod/Sketcher/App/CMakeLists.txt | 3 + src/Mod/Sketcher/App/SketchAnalysis.cpp | 808 ++++++++++++++++++++++++ src/Mod/Sketcher/App/SketchAnalysis.h | 144 +++++ 4 files changed, 1017 insertions(+) create mode 100644 src/Mod/Sketcher/App/Analyse.h create mode 100644 src/Mod/Sketcher/App/SketchAnalysis.cpp create mode 100644 src/Mod/Sketcher/App/SketchAnalysis.h diff --git a/src/Mod/Sketcher/App/Analyse.h b/src/Mod/Sketcher/App/Analyse.h new file mode 100644 index 0000000000..87d89de6a0 --- /dev/null +++ b/src/Mod/Sketcher/App/Analyse.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (c) 2018 Abdullah Tahiri * + * Copyright (c) 2013 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 * + * * + ***************************************************************************/ + + +#ifndef SKETCHER_ANALYSE_H +#define SKETCHER_ANALYSE_H + +#include +#include + +namespace Sketcher { + +struct ConstraintIds { + Base::Vector3d v; + int First; + int Second; + Sketcher::PointPos FirstPos; + Sketcher::PointPos SecondPos; + Sketcher::ConstraintType Type; +}; + +struct Constraint_Equal : public std::unary_function +{ + struct Sketcher::ConstraintIds c; + Constraint_Equal(const ConstraintIds& c) : c(c) + { + } + bool operator()(const ConstraintIds& x) const + { + if (c.First == x.First && c.FirstPos == x.FirstPos && + c.Second == x.Second && c.SecondPos == x.SecondPos) + return true; + if (c.Second == x.First && c.SecondPos == x.FirstPos && + c.First == x.Second && c.FirstPos == x.SecondPos) + return true; + return false; + } +}; + +} //namespace Sketcher + +#endif // SKETCHER_ANALYSE_H diff --git a/src/Mod/Sketcher/App/CMakeLists.txt b/src/Mod/Sketcher/App/CMakeLists.txt index 0d5051b5a2..4667cd1358 100644 --- a/src/Mod/Sketcher/App/CMakeLists.txt +++ b/src/Mod/Sketcher/App/CMakeLists.txt @@ -52,6 +52,9 @@ SET(Features_SRCS SketchObjectSF.h SketchObject.cpp SketchObject.h + SketchAnalysis.h + SketchAnalysis.cpp + Analyse.h ) SOURCE_GROUP("Features" FILES ${Features_SRCS}) diff --git a/src/Mod/Sketcher/App/SketchAnalysis.cpp b/src/Mod/Sketcher/App/SketchAnalysis.cpp new file mode 100644 index 0000000000..5beff47359 --- /dev/null +++ b/src/Mod/Sketcher/App/SketchAnalysis.cpp @@ -0,0 +1,808 @@ +/*************************************************************************** + * Copyright (c) 2018 Abdullah Tahiri * + * Copyright (c) 2013 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "SketchAnalysis.h" + +using namespace Sketcher; + +SketchAnalysis::SketchAnalysis(Sketcher::SketchObject* Obj) + : sketch(Obj) +{ + +} + +SketchAnalysis::~SketchAnalysis() +{ + +} + +struct SketchAnalysis::VertexIds { + Base::Vector3d v; + int GeoId; + Sketcher::PointPos PosId; +}; + +struct SketchAnalysis::Vertex_Less : public std::binary_function +{ + 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 SketchAnalysis::Vertex_EqualTo : public std::binary_function +{ + 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 SketchAnalysis::EdgeIds { + double l; + int GeoId; +}; + +struct SketchAnalysis::Edge_Less : public std::binary_function +{ + 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 SketchAnalysis::Edge_EqualTo : public std::binary_function +{ + Edge_EqualTo(double tolerance) : tolerance(tolerance){} + bool operator()(const EdgeIds& x, + const EdgeIds& y) const + { + if (fabs (x.l - y.l) <= tolerance) { + return true; + } + return false; + } +private: + double tolerance; +}; + +int SketchAnalysis::detectMissingPointOnPointConstraints(double precision, bool includeconstruction /*=true*/) +{ + std::vector vertexIds; + const std::vector& geom = sketch->getInternalGeometry(); + for (std::size_t i=0; iConstruction && !includeconstruction) + continue; + + if (g->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(); + vertexIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(/*emulateCCW=*/true); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(/*emulateCCW=*/true); + vertexIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) { + const Part::GeomArcOfEllipse *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(/*emulateCCW=*/true); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(/*emulateCCW=*/true); + vertexIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) { + const Part::GeomArcOfHyperbola *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(); + vertexIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) { + const Part::GeomArcOfParabola *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(); + vertexIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + const Part::GeomBSplineCurve *segm = static_cast(g); + VertexIds id; + id.GeoId = (int)i; + id.PosId = Sketcher::start; + id.v = segm->getStartPoint(); + vertexIds.push_back(id); + id.GeoId = (int)i; + id.PosId = Sketcher::end; + id.v = segm->getEndPoint(); + vertexIds.push_back(id); + } + } + + std::sort(vertexIds.begin(), vertexIds.end(), Vertex_Less(precision)); + std::vector::iterator vt = vertexIds.begin(); + Vertex_EqualTo pred(precision); + + std::list coincidences; + // Make a list of constraint we expect for coincident vertexes + while (vt < vertexIds.end()) { + // get first item whose adjacent element has the same vertex coordinates + vt = std::adjacent_find(vt, vertexIds.end(), pred); + if (vt < vertexIds.end()) { + std::vector::iterator vn; + for (vn = vt+1; vn != vertexIds.end(); ++vn) { + if (pred(*vt,*vn)) { + ConstraintIds id; + id.Type = Coincident; // default point on point restriction + id.v = vt->v; + id.First = vt->GeoId; + id.FirstPos = vt->PosId; + id.Second = vn->GeoId; + id.SecondPos = vn->PosId; + coincidences.push_back(id); + } + else { + break; + } + } + + vt = vn; + } + } + + // 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 constraint = sketch->Constraints.getValues(); + for (std::vector::iterator it = constraint.begin(); it != constraint.end(); ++it) { + if ((*it)->Type == Sketcher::Coincident || + (*it)->Type == Sketcher::Tangent || + (*it)->Type == Sketcher::Perpendicular) { + ConstraintIds id; + id.First = (*it)->First; + id.FirstPos = (*it)->FirstPos; + id.Second = (*it)->Second; + id.SecondPos = (*it)->SecondPos; + std::list::iterator pos = std::find_if + (coincidences.begin(), coincidences.end(), Constraint_Equal(id)); + if (pos != coincidences.end()) { + coincidences.erase(pos); + } + } + } + + this->vertexConstraints.clear(); + this->vertexConstraints.reserve(coincidences.size()); + + for (std::list::iterator it = coincidences.begin(); it != coincidences.end(); ++it) { + this->vertexConstraints.push_back(*it); + } + + return 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 Part::GeomCurve * curve1 = dynamic_cast(geo1); + const Part::GeomCurve * curve2 = dynamic_cast(geo2); + + if(curve1 && curve2) { + + if( geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && + geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + + const Part::GeomLineSegment *segm1 = static_cast(geo1); + const Part::GeomLineSegment *segm2 = static_cast(geo2); + + 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, 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) constr; + + for (std::vector::iterator it = vertexConstraints.begin(); it != vertexConstraints.end(); ++it) { + Sketcher::Constraint* c = new Sketcher::Constraint(); + c->Type = it->Type; + c->First = it->First; + c->Second = it->Second; + c->FirstPos = it->FirstPos; + c->SecondPos = it->SecondPos; + + if(onebyone) { + sketch->addConstraint(c); + + solvesketch(status,dofs,true); + + if(status == -2) { //redundant constraints + autoRemoveRedundants(false); + + solvesketch(status,dofs,false); + } + + if(status) { + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch while applying coincident constraints.\n")) + } + } + else { + constr.push_back(c); + } + } + + if(!onebyone) + sketch->addConstraints(constr); + + vertexConstraints.clear(); + + for (std::vector::iterator it = constr.begin(); it != constr.end(); ++it) { + delete *it; + } +} + +int SketchAnalysis::detectMissingVerticalHorizontalConstraints(double angleprecision) +{ + const std::vector& geom = sketch->getInternalGeometry(); + + verthorizConstraints.clear(); + + for (std::size_t i=0; igetTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *segm = static_cast(g); + + Base::Vector3d dir = segm->getEndPoint() - segm->getStartPoint(); + + ConstraintIds id; + + id.v = dir; + id.First = (int)i; + id.FirstPos = Sketcher::none; + id.Second = Constraint::GeoUndef; + id.SecondPos = Sketcher::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 verthorizConstraints.size(); +} + +void SketchAnalysis::makeMissingVerticalHorizontal(bool onebyone) +{ + int status, dofs; + std::vector constr; + + for (std::vector::iterator it = verthorizConstraints.begin(); it != verthorizConstraints.end(); ++it) { + Sketcher::Constraint* c = new Sketcher::Constraint(); + c->Type = it->Type; + c->First = it->First; + c->Second = it->Second; + c->FirstPos = it->FirstPos; + c->SecondPos = it->SecondPos; + + if(onebyone) { + sketch->addConstraint(c); + + solvesketch(status,dofs,true); + + if(status == -2) { //redundant constraints + autoRemoveRedundants(false); + + solvesketch(status,dofs,false); + } + + if(status) { + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch while applying vertical/horizontal constraints.\n")) + } + } + else { + constr.push_back(c); + } + } + + if(!onebyone) + sketch->addConstraints(constr); + + verthorizConstraints.clear(); + + for (std::vector::iterator it = constr.begin(); it != constr.end(); ++it) { + delete *it; + } +} + +bool SketchAnalysis::checkVertical(Base::Vector3d dir, double angleprecision) +{ + return (dir.x == 0. && dir.y != 0.) || ( fabs(dir.y/dir.x) > tan(M_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) +{ + std::vector lineedgeIds; + std::vector radiusedgeIds; + + const std::vector& geom = sketch->getInternalGeometry(); + for (std::size_t i=0; igetTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *segm = static_cast(g); + EdgeIds id; + id.GeoId = (int)i; + id.l = (segm->getEndPoint()-segm->getStartPoint()).Length(); + lineedgeIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *segm = static_cast(g); + EdgeIds id; + id.GeoId = (int)i; + id.l = segm->getRadius(); + radiusedgeIds.push_back(id); + } + else if (g->getTypeId() == Part::GeomCircle::getClassTypeId()) { + const Part::GeomCircle *segm = static_cast(g); + EdgeIds id; + id.GeoId = (int)i; + id.l = segm->getRadius(); + radiusedgeIds.push_back(id); + } + } + + std::sort(lineedgeIds.begin(), lineedgeIds.end(), Edge_Less(precision)); + std::vector::iterator vt = lineedgeIds.begin(); + Edge_EqualTo pred(precision); + + std::list 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::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::none; + id.Second = vn->GeoId; + id.SecondPos = Sketcher::none; + equallines.push_back(id); + } + else { + break; + } + } + + vt = vn; + } + } + + std::sort(radiusedgeIds.begin(), radiusedgeIds.end(), Edge_Less(precision)); + vt = radiusedgeIds.begin(); + + std::list 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::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::none; + id.Second = vn->GeoId; + id.SecondPos = Sketcher::none; + equalradius.push_back(id); + } + else { + break; + } + } + + vt = vn; + } + } + + + // 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 constraint = sketch->Constraints.getValues(); + for (std::vector::iterator it = constraint.begin(); it != constraint.end(); ++it) { + if ((*it)->Type == Sketcher::Equal) { + ConstraintIds id; + id.First = (*it)->First; + id.FirstPos = (*it)->FirstPos; + id.Second = (*it)->Second; + id.SecondPos = (*it)->SecondPos; + + std::list::iterator 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 (std::list::iterator it = equallines.begin(); it != equallines.end(); ++it) { + this->lineequalityConstraints.push_back(*it); + } + + this->radiusequalityConstraints.clear(); + this->radiusequalityConstraints.reserve(equalradius.size()); + + for (std::list::iterator it = equalradius.begin(); it != equalradius.end(); ++it) { + this->radiusequalityConstraints.push_back(*it); + } + + return this->lineequalityConstraints.size() + this->radiusequalityConstraints.size(); +} + +void SketchAnalysis::makeMissingEquality(bool onebyone) +{ + int status, dofs; + std::vector constr; + + std::vector equalities(lineequalityConstraints); + equalities.insert(equalities.end(),radiusequalityConstraints.begin(), radiusequalityConstraints.end()); + + for (std::vector::iterator it = equalities.begin(); it != equalities.end(); ++it) { + Sketcher::Constraint* c = new Sketcher::Constraint(); + c->Type = it->Type; + c->First = it->First; + c->Second = it->Second; + c->FirstPos = it->FirstPos; + c->SecondPos = it->SecondPos; + + if(onebyone) { + sketch->addConstraint(c); + + solvesketch(status,dofs,true); + + if(status == -2) { //redundant constraints + autoRemoveRedundants(false); + + solvesketch(status,dofs,false); + } + + if(status) { + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch while applying equality constraints.\n")) + } + } + else { + constr.push_back(c); + } + } + + if(!onebyone) + sketch->addConstraints(constr); + + lineequalityConstraints.clear(); + radiusequalityConstraints.clear(); + + for (std::vector::iterator it = constr.begin(); it != constr.end(); ++it) { + delete *it; + } +} + +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()) { // redundant constraints + status = -2; + } + + if (dofs < 0) { // over-constrained sketch + status = -4; + } + else if (sketch->getLastHasConflicts()) { // conflicting constraints + status = -3; + } +} + +void SketchAnalysis::autoRemoveRedundants(bool updategeo) +{ + auto redundants = sketch->getLastRedundant(); + + for(size_t i=0;idelConstraints(redundants,updategeo); +} + +int SketchAnalysis::autoconstraint(double precision, double angleprecision, bool includeconstruction) +{ + App::Document* doc = sketch->getDocument(); + doc->openTransaction("delete all constraints"); + // We start from zero + sketch->deleteAllConstraints(); + + doc->commitTransaction(); + + int status, dofs; + + solvesketch(status,dofs,true); + + if(status) {// it should not be possible at this moment as we start from a clean situation + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch without constraints.\n")) + } + + // 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 ) { + App::Document* doc = sketch->getDocument(); + doc->openTransaction("add vertical/horizontal constraints"); + + makeMissingVerticalHorizontal(); + + // finish the transaction and update + doc->commitTransaction(); + + solvesketch(status,dofs,true); + + if(status == -2) { // redundants + autoRemoveRedundants(false); + solvesketch(status,dofs,false); + } + + if(status) { + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch after applying horizontal and vertical constraints.\n")) + } + } + + // Applying STAGE 2 + if(nc > 0) { + App::Document* doc = sketch->getDocument(); + doc->openTransaction("add coincident constraint"); + + makeMissingPointOnPointCoincident(); + + // finish the transaction and update + doc->commitTransaction(); + + solvesketch(status,dofs,true); + + if(status == -2) { // redundants + autoRemoveRedundants(false); + solvesketch(status,dofs,false); + } + + if(status) { + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch after applying point-on-point constraints.\n")) + } + } + + // Applying STAGE 3 + if(ne > 0) { + App::Document* doc = sketch->getDocument(); + doc->openTransaction("add equality constraints"); + + try { + makeMissingEquality(); + } + catch(Base::RuntimeError e) + { + doc->abortTransaction(); + throw; + } + + // finish the transaction and update + doc->commitTransaction(); + + solvesketch(status,dofs,true); + + if(status == -2) { // redundants + autoRemoveRedundants(false); + solvesketch(status,dofs,false); + } + + if(status) { + THROWMT(Base::RuntimeError, QT_TRANSLATE_NOOP("Exceptions", "Autoconstrain error: Unsolvable sketch after applying equality constraints.\n")) + } + } + + + return 0; +} \ No newline at end of file diff --git a/src/Mod/Sketcher/App/SketchAnalysis.h b/src/Mod/Sketcher/App/SketchAnalysis.h new file mode 100644 index 0000000000..74eeebe6fa --- /dev/null +++ b/src/Mod/Sketcher/App/SketchAnalysis.h @@ -0,0 +1,144 @@ +/*************************************************************************** + * Copyright (c) 2018 Abdullah Tahiri * + * Copyright (c) 2013 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 * + * * + ***************************************************************************/ + + +#ifndef SKETCHER_SKETCHANALYSIS_H +#define SKETCHER_SKETCHANALYSIS_H + +#include +#include +#include +#include "Analyse.h" +#include "SketchObject.h" + +namespace Sketcher { + +class SketchObject; + +class SketchAnalysis +{ +public: + /// Creates an instance of the SketchAnalysis object, taking as parameter a pointer to an SketchObject. + /// + /// There is a first type of routines, simple routines, which work in the following order: + /// Detect - (Analyse) - [Get] - [Set] - Make + /// + /// The Detect step just identifies possible missing constraints. + /// + /// The Analyse, which is not available for all the routines, operates in detected constraints of the same routine, to + /// look for alternatives. For example, a general pointonpoint detection leads to a search for coincident constraints, which + /// can be later run via Analyse if it is intended to convert endpoint coincidence to endpoint perpendicular and tangent constraints. + /// + /// The Get retrieves the result of the analysis as a vector of ConstraintIds, indicating the suggested constraints. This step is intended + /// for enabling the user to check the result of the analysis, rather than applying it. If only applying is intended, this step is not necessary + /// as the Make will operate on the result of the Detect - Analyse directly. + /// + /// The Set changes the detected result. It modifies the SketchAnalysis object. It only modifies the SketchObject as far as the SketchAnalysis is changed. + /// It does not apply any changes to the sketch. It is intended so as to enable the user to change the result that will be applied. + /// + /// Neither the Detect, nor the Analyse, nor the Get steps modify the Sketch geometry. + /// + /// Make applies the constraints stored internally in the SketchAnalysis object. + /// + /// A second type of routines, complex routines, are thought for running fully automatic and they Detect, Analyse and Make. + /// They may also apply a variaty of types of Constraints. + SketchAnalysis(Sketcher::SketchObject * Obj); + ~SketchAnalysis(); + + // Simple routines (see constructor) + + /// Point on Point constraint simple routine Detect step (see constructor) + /// Detect detects only coincident constraints, Analyse converts coincident to endpoint perpendicular/tangent where appropriate + int detectMissingPointOnPointConstraints(double precision = Precision::Confusion() * 1000, bool includeconstruction = true); + /// Point on Point constraint simple routine Analyse step (see constructor) + void analyseMissingPointOnPointCoincident(double angleprecision = M_PI/8); + /// Point on Point constraint simple routine Get step (see constructor) + std::vector &getMissingPointOnPointConstraints(void) {return vertexConstraints;}; + /// Vertical/Horinzontal constraints simple routine Set step (see constructor) + void setMissingPointOnPointConstraints(std::vector& cl) {vertexConstraints = cl;}; + /// Point on Point constraint simple routine Make step (see constructor) + /// if onebyone, then the sketch is solved after each individual constraint addition and any redundancy removed. + void makeMissingPointOnPointCoincident(bool onebyone = false); + + /// Vertical/Horizontal constraints simple routine Detect step (see constructor) + int detectMissingVerticalHorizontalConstraints(double angleprecision = M_PI/8); + /// Vertical/Horizontal constraints simple routine Get step (see constructor) + std::vector &getMissingVerticalHorizontalConstraints(void) {return verthorizConstraints;}; + /// Vertical/Horinzontal constraints simple routine Set step (see constructor) + void setMissingVerticalHorizontalConstraints(std::vector& cl) {verthorizConstraints = cl;}; + /// Vertical/Horizontal constraints simple routine Make step (see constructor) + void makeMissingVerticalHorizontal(bool onebyone = false); + + /// Equality constraints simple routine Detect step (see constructor) + int detectMissingEqualityConstraints(double precision); + /// Equality constraints simple routine Get step for line segments (see constructor) + std::vector &getMissingLineEqualityConstraints(void) {return lineequalityConstraints;}; + /// Equality constraints simple routine Get step for radii (see constructor) + std::vector &getMissingRadiusConstraints(void) {return radiusequalityConstraints;}; + /// Equality constraints simple routine Set step for line segments (see constructor) + void setMissingLineEqualityConstraints(std::vector& cl) {lineequalityConstraints = cl;}; + /// Equality constraints simple routine Set step for radii (see constructor) + void setMissingRadiusConstraints(std::vector& cl) {radiusequalityConstraints = cl;}; + /// Equality constraints simple routine Make step (see constructor) + void makeMissingEquality(bool onebyone = true); + + // Complex routines (see constructor) + + /// Fully automated multi-constraint autoconstraining + /// + /// It DELETES all the constraints currently present in the Sketcher. The reason is that it makes assumptions to avoid redundancies. + /// + /// It applies coincidents - vertical/horizontal constraints and equality constraints. + int autoconstraint(double precision = Precision::Confusion() * 1000, double angleprecision = M_PI/8, bool includeconstruction = true); + + // helper functions, which may be used by more complex methods, and/or called directly by user space (python) methods + + /// solves the sketch and retrieves the error status, and the degrees of freedom. + /// It enables to solve updating the geometry (so moving the geometry to match the constraints) or preserving the geometry. + void solvesketch(int &status, int &dofs, bool updategeo); + /// It gets the redundant constraints from the solver and deletes them + void autoRemoveRedundants(bool updategeo); + +protected: + Sketcher::SketchObject* sketch; + + struct VertexIds; + struct Vertex_Less; + struct Vertex_EqualTo; + struct EdgeIds; + struct Edge_Less; + struct Edge_EqualTo; + std::vector vertexConstraints; + std::vector verthorizConstraints; + std::vector lineequalityConstraints; + std::vector radiusequalityConstraints; + +protected: + bool checkHorizontal(Base::Vector3d dir, double angleprecision); + bool checkVertical(Base::Vector3d dir, double angleprecision); + +}; + +} //namespace Sketcher + +#endif // SKETCHER_SKETCHANALYSIS_H